feat: migre le quiz, renomme en Lagé Chat Control, nettoie l'ère Astro
- Quiz de résistance (/quiz) migré d'Astro vers Svelte 5 : 12 questions, contenu fusionné dans content/quiz.json, score calculé localement (aucun envoi), barres par domaine, recommandations liées à l'annuaire réel (/outils?cat=…), persistance locale, partage natif/presse-papier. Audit axe-core : 0 violation. - Renommage complet « Exit Chat Control » → « Lagé Chat Control » : i18n fr/gcf, manifest PWA, package.json, README, design system. - README racine et frontend/README.md réécrits pour le projet Svelte : lancement local (pnpm dev) et déploiement VPS (renvoi vers docs/DEPLOYMENT.md), en français. - CONTRIBUTING.md adapté à la nouvelle structure (content/outils/, forge labola) en conservant la politique éditoriale de confiance. - Nettoyage : suppression de tout l'outillage Astro devenu inutile (src/, public/, scripts/, tests/, migration/, docker/, configs racine eslint/prettier/vitest/playwright/tsconfig, CI GitHub Actions, script de migration one-shot). robots.txt et security.txt recréés sous frontend/static/ avec les bonnes références (labola). - Fix : pin pnpm via frontend/pnpm-workspace.yaml (onlyBuiltDependencies/ allowBuilds pour esbuild) après suppression du package.json racine qui perturbait la résolution de version de corepack. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python3 -c ' *)",
|
||||
"Bash(pnpm --version)",
|
||||
"Bash(curl -sI --max-time 8 https://registry.npmjs.org/svelte)",
|
||||
"Bash(pnpm install *)",
|
||||
"Bash(awk -F: '{s+=$2} END {print \"ToolCards total:\",s}')",
|
||||
"Bash(node scripts/migrate-tools.mjs)",
|
||||
"Bash(cp ../public/favicon.png static/)",
|
||||
"Bash(pnpm add *)",
|
||||
"Bash(pnpm build *)",
|
||||
"Bash(pnpm check *)",
|
||||
"Bash(git add *)",
|
||||
"Bash(git remote *)",
|
||||
"Bash(git ls-remote *)",
|
||||
"Bash(git push *)",
|
||||
"Bash(git config *)",
|
||||
"Read(//home/sucupira/.ssh/**)",
|
||||
"Bash(timeout 10 ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -T git@labola.o-k-i.net)",
|
||||
"Bash(git fetch *)",
|
||||
"Bash(cd \"/home/sucupira/NHEDKXONE/Activités/Projects/exitchatcontrol.org/frontend\" && node scripts/migrate-tools.mjs && grep -n 'IA\\\\|os' ../content/categories.json | head -4)",
|
||||
"Bash(NODE_PATH=./node_modules node --experimental-default-type=module /tmp/claude-1000/-home-sucupira-NHEDKXONE-Activit-s-Projects-exitchatcontrol-org/8c49884d-98d3-4232-9bbe-1c9796e85319/scratchpad/axe-audit.mjs)",
|
||||
"Bash(cp /tmp/claude-1000/-home-sucupira-NHEDKXONE-Activit-s-Projects-exitchatcontrol-org/8c49884d-98d3-4232-9bbe-1c9796e85319/scratchpad/axe-audit.mjs ./axe-audit.tmp.mjs)",
|
||||
"Bash(node ./axe-audit.tmp.mjs)",
|
||||
"Bash(cd \"/home/sucupira/NHEDKXONE/Activités/Projects/exitchatcontrol.org\" && ls node_modules 2>/dev/null | head -3; ls ~/.cache/ms-playwright 2>/dev/null)",
|
||||
"Bash(node scripts/audit-a11y.mjs)",
|
||||
"Bash(npx playwright *)",
|
||||
"Bash(CHROME_PATH=/home/sucupira/.cache/ms-playwright/chromium-1228/chrome-linux64/chrome npx --yes lighthouse http://localhost:4173/ --quiet --chrome-flags=\"--headless --no-sandbox\" --output=json --output-path=/tmp/claude-1000/-home-sucupira-NHEDKXONE-Activit-s-Projects-exitchatcontrol-org/8c49884d-98d3-4232-9bbe-1c9796e85319/scratchpad/lh-home.json --only-categories=performance,accessibility,best-practices,seo)",
|
||||
"Bash(curl -s http://localhost:4173/)",
|
||||
"Bash(git checkout *)",
|
||||
"Bash(grep -v '^d.*\\\\.git$')",
|
||||
"Bash(git rm *)",
|
||||
"Bash(pnpm approve-builds *)",
|
||||
"Bash(git commit -m ' *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
.astro
|
||||
.git
|
||||
playwright-report
|
||||
test-results
|
||||
index.html
|
||||
images
|
||||
@@ -1,48 +0,0 @@
|
||||
# CI: lint → typecheck → unit tests → build → e2e (incl. axe + zero-request).
|
||||
# Actions are SHA-pinned; no ${{ github.event.* }} is ever interpolated into
|
||||
# run: blocks (script-injection hygiene). Fork PRs run with a read-only token.
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, rebuild/astro]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- run: pnpm lint
|
||||
- run: pnpm format
|
||||
- run: pnpm check
|
||||
- run: pnpm build
|
||||
# tests run after build: the domain-allowlist suite audits dist/
|
||||
- run: pnpm test
|
||||
|
||||
- name: Install Playwright chromium
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- run: pnpm test:e2e
|
||||
|
||||
- name: Upload Playwright report on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 7
|
||||
@@ -1,38 +0,0 @@
|
||||
# Weekly link-rot watch: every cited source URL is checked over HTTP.
|
||||
# Surveillance archives decay — this applies the lesson to ourselves.
|
||||
# On failure it opens a triage issue (runner-provided vars only — nothing
|
||||
# attacker-controlled is interpolated).
|
||||
name: link-rot
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1' # Mondays 06:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Check cited sources
|
||||
run: node scripts/check-links.mjs
|
||||
|
||||
- name: Open triage issue on failure
|
||||
if: failure()
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh issue create \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "link-rot: cited sources failing ($(date -u +%F))" \
|
||||
--body "The weekly link check failed. See the run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
|
||||
+1
-5
@@ -1,11 +1,7 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.astro/
|
||||
playwright-report/
|
||||
test-results/
|
||||
*.local
|
||||
.DS_Store
|
||||
|
||||
# SvelteKit (frontend/)
|
||||
.svelte-kit/
|
||||
frontend/.svelte-kit/
|
||||
frontend/build/
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
dist/
|
||||
.astro/
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
index.html
|
||||
src/icons.generated.ts
|
||||
src/styles/brands.generated.css
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-astro"],
|
||||
"overrides": [{ "files": "*.astro", "options": { "parser": "astro" } }]
|
||||
}
|
||||
+26
-22
@@ -1,40 +1,44 @@
|
||||
# Contributing to Exit Chat Control
|
||||
# Contribuer à Lagé Chat Control
|
||||
|
||||
Contributions are welcome — this guide exists because people share what works. But this is a **trust-first** project: readers follow our recommendations to protect themselves from surveillance. That trust is the product, so the rules below are strict and non-negotiable. Every submission is reviewed personally by the maintainer.
|
||||
Les contributions sont bienvenues — ce guide existe parce que les gens partagent ce qui marche. Mais c'est un projet où **la confiance prime** : les lecteurs et lectrices suivent nos recommandations pour se protéger de la surveillance. Cette confiance est le produit, donc les règles ci-dessous sont strictes et non négociables. Chaque soumission est relue personnellement.
|
||||
|
||||
## Adding a tool or resource
|
||||
Pour les gestes simples (proposer un outil, corriger une fiche), la page [/contribuer](frontend/src/routes/contribuer/+page.svelte) du site et le [guide de contenu](docs/CONTENT_GUIDE.md) suffisent. Ce document couvre la politique éditoriale et les changements plus larges.
|
||||
|
||||
A tool must meet all three of the original rules:
|
||||
## Ajouter un outil
|
||||
|
||||
1. **Traction** — an established, actively maintained project with a real user base. Not an obscure or abandoned tool, and not your three-month-old project.
|
||||
2. **Open source** — a public repository with an OSI-approved license.
|
||||
3. **Privacy-respecting** — no trackers, no data monetization, honest documentation.
|
||||
Un outil doit remplir les trois règles :
|
||||
|
||||
## Disclosure of affiliation (mandatory)
|
||||
1. **Traction** — un projet établi, activement maintenu, avec un vrai usage. Pas un outil obscur ou abandonné, pas ton projet vieux de trois mois.
|
||||
2. **Open source** — un dépôt public sous licence OSI reconnue.
|
||||
3. **Respectueux de la vie privée** — pas de traqueur, pas de monétisation des données, documentation honnête.
|
||||
|
||||
If you are the author, maintainer, employee, investor, or otherwise affiliated with a tool, service, or website you propose to add or link, **you must say so in the PR description**. Undisclosed self-promotion — however good the surrounding work — gets the PR closed and the account reported for inauthentic activity. This has happened before; it will happen again.
|
||||
## Déclaration d'affiliation (obligatoire)
|
||||
|
||||
## Link policy (enforced by CI)
|
||||
Si tu es autrice, mainteneur, salarié·e, investisseur ou autrement affilié·e à un outil que tu proposes, **dis-le dans la description de la demande**. Une auto-promotion non déclarée — même si le reste du travail est bon — entraîne la fermeture de la demande.
|
||||
|
||||
- Every external link is rendered through the `Ext` component, which enforces `rel="noopener noreferrer"`. No per-link `rel` exceptions, for anyone, ever.
|
||||
- Every external domain must be listed in `src/data/domains-allowlist.json`. The test suite fails if the built site links to a domain that isn't allowlisted — adding a domain is therefore always an explicit, reviewable diff.
|
||||
- Editorial placements ("featured", "guide's pick", ranking changes) are the maintainer's decision only. PRs that visually elevate one tool above others will be asked to remove that change.
|
||||
## Politique des liens
|
||||
|
||||
## Content and facts
|
||||
- Chaque outil référencé doit avoir une licence SPDX identifiable (voir [`content/outils/`](content/outils/)).
|
||||
- Les mises en avant éditoriales (`is_featured`, ordre des catégories) restent une décision de maintenance — une demande qui met en avant un outil au détriment des autres sera priée de revenir en arrière.
|
||||
|
||||
- Every dated claim (timeline, observatory) needs a source URL, primary wherever possible (legislation, court rulings, institutional press releases — not blog posts).
|
||||
- Translations: UI strings live in `src/i18n/<lang>.json`; long-form content in `src/content/sections/<lang>/`; dataset prose in `src/i18n/content/<lang>/`. Adding a language = adding those files plus one line in `src/i18n/locales.ts`. Parity is tested.
|
||||
## Contenu et faits
|
||||
|
||||
## Large changes
|
||||
- Chaque affirmation datée a besoin d'une source, primaire si possible (texte de loi, décision de justice, communiqué institutionnel — pas un article de blog).
|
||||
- Traductions : les micro-textes d'interface vivent dans `frontend/src/lib/i18n/<locale>.json` (clé manquante = erreur de build) ; le contenu des fiches dans `content/outils/` (FR) et `content/outils/gcf/` (jumelles créoles, voir [CONTENT_GUIDE.md](docs/CONTENT_GUIDE.md)).
|
||||
|
||||
Open an issue **before** writing a large PR (redesigns, stack changes, new sections). Unsolicited full rebuilds are closed by default — not because the work is bad, but because review cost and trust cost scale with diff size.
|
||||
## Changements de grande ampleur
|
||||
|
||||
## Development
|
||||
Ouvre une discussion **avant** d'écrire une grosse demande (refontes, changement de stack, nouvelles sections). Les reconstructions complètes non sollicitées sont fermées par défaut — pas parce que le travail est mauvais, mais parce que le coût de relecture et de confiance croît avec la taille du diff.
|
||||
|
||||
## Développement
|
||||
|
||||
```sh
|
||||
cd frontend
|
||||
pnpm install
|
||||
pnpm dev # dev server
|
||||
pnpm lint && pnpm check && pnpm build && pnpm test # what CI runs (tests audit dist/)
|
||||
pnpm dev # serveur de développement
|
||||
pnpm check # types + accessibilité (svelte-check)
|
||||
pnpm build # build statique complet
|
||||
pnpm audit:a11y # audit axe-core sur le build servi en local
|
||||
```
|
||||
|
||||
The site must remain: static, readable with JavaScript disabled, free of any request to a third-party domain, and printable. Tests enforce all four.
|
||||
Le site doit rester : statique, lisible sans JavaScript pour tout le contenu et les formulaires de base, sans requête vers un domaine tiers. Voir [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) pour la mise en production.
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# ── build: Astro static site + offline artifact + CSP hashes ──────────────
|
||||
FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 AS build
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
ENV ASTRO_TELEMETRY_DISABLED=1
|
||||
RUN pnpm build \
|
||||
&& find dist -type f \( -name '*.html' -o -name '*.css' -o -name '*.js' \
|
||||
-o -name '*.svg' -o -name '*.xml' -o -name '*.txt' -o -name '*.webmanifest' \) \
|
||||
-exec gzip -9k {} \;
|
||||
|
||||
# ── serve: nginx, ~10MB, security headers, precompressed assets ───────────
|
||||
FROM nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY docker/security-headers.conf /etc/nginx/snippets/security-headers.conf
|
||||
# csp.conf is generated by scripts/gen-csp.mjs during `pnpm build`
|
||||
COPY --from=build /app/docker/csp.conf /etc/nginx/snippets/csp.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
@@ -1,44 +1,59 @@
|
||||
# Exit Chat Control
|
||||
# Lagé Chat Control
|
||||
|
||||
A free, multilingual field guide to escape **Chat Control** and take back control of your digital privacy: encrypted messaging, email, VPN, DNS, 2FA, Linux, GrapheneOS, self-hosting, and more. Every tool is presented with **what it does**, **who it's for**, and **how to install it**.
|
||||
Un guide de terrain, libre et bilingue (français / kréyòl), pour échapper à **Chat Control** et reprendre le contrôle de sa vie privée numérique : messagerie chiffrée, e-mail, VPN, DNS, 2FA, Linux, GrapheneOS, auto-hébergement, et plus. Chaque outil est présenté avec **à quoi il sert**, **pour qui** et **comment l'installer**.
|
||||
|
||||
🔗 **Live site: https://exitchatcontrol.org**
|
||||
Projet OKI (Organisation Ka Internationale) — voir [`ADR.md`](ADR.md) pour la décision d'architecture et [`docs/`](docs/) pour la doctrine UI/UX, le design system et le guide de contenu.
|
||||
|
||||
## What is this?
|
||||
## En bref
|
||||
|
||||
In July 2026, the European Parliament let the "Chat Control" machinery advance — the amended text now sits with the Council, and the permanent version (CSAR, "Chat Control 2.0") is still being negotiated. This project is a practical, no-nonsense answer: a guide referencing privacy-respecting tools anyone can use to opt out of mass surveillance, from the everyday citizen to the whistleblower.
|
||||
|
||||
Its promises, all enforced by tests:
|
||||
|
||||
- **Static and self-contained** — no tracker, no cookie, not a single request to a third-party domain.
|
||||
- **Readable with JavaScript disabled** (Tor Browser "safest" mode included) — scripts only power the theme, filters, checklist and quiz.
|
||||
- **Multilingual by design** — every language is a real prerendered route with correct `hreflang`; adding one means adding translation files, not touching code.
|
||||
- **Educational quiz** — `/quiz` scores your censorship resistance on a 0–100 scale and points you at concrete fixes in the guide. Server-rendered questions and scoring key (readable with JS off); the score itself is computed locally in the browser, nothing sent or stored.
|
||||
- **Printable and mirrorable** — a single-file offline version ships with every build.
|
||||
- **Statique et auto-suffisant** — pas de traqueur, pas de cookie, aucune requête vers un domaine tiers, aucun CDN (polices et assets auto-hébergés).
|
||||
- **Lisible sans JavaScript** — le contenu et les formulaires de base fonctionnent nus ; le script n'enrichit que le quiz, la recherche instantanée et les filtres.
|
||||
- **Bilingue FR / kréyòl** — les micro-textes d'interface sont typés (une clé manquante = erreur de build) ; le contenu des fiches suit le même principe avec repli explicite quand une traduction manque.
|
||||
- **Quiz éducatif** — `/quiz` évalue ta résistance à la censure sur 100 points et pointe vers des pistes concrètes dans l'annuaire. Calcul entièrement local, rien n'est envoyé ni enregistré.
|
||||
- **PWA hors-ligne** — les fiches déjà consultées restent lisibles sans connexion.
|
||||
|
||||
## Stack
|
||||
|
||||
[Astro](https://astro.build) (static output, built-in i18n routing) · Tailwind CSS v4 (design tokens, `light-dark()` theming) · TypeScript strict · Vitest (content integrity, i18n parity, domain allowlist) · Playwright + axe (no-JS readability, zero-third-party-request guarantee, WCAG AA) · Docker (multi-stage build → nginx). Package manager: pnpm.
|
||||
**Svelte 5 + SvelteKit 2**, site 100 % prérendu (`adapter-static`) · **CSS natif** (`@layer`, `oklch()`, container queries, `clamp()` — pas de Tailwind) · contenu en fichiers Markdown/JSON versionnés dans [`content/`](content/), aucun CMS ni base de données. Gestionnaire de paquets : pnpm.
|
||||
|
||||
### Lancer en local
|
||||
|
||||
```sh
|
||||
cd frontend
|
||||
pnpm install
|
||||
pnpm dev # develop on http://localhost:4321
|
||||
pnpm build # static site + offline artifact in dist/
|
||||
docker compose -f docker-compose.dev.yml up preview # prod-like: nginx + security headers on :8080
|
||||
pnpm dev # http://localhost:5173
|
||||
```
|
||||
|
||||
Production deploys `docker-compose.yml` (single nginx service behind the platform's reverse proxy — Coolify, etc.), or point any static host at `pnpm build` → `dist/`.
|
||||
```sh
|
||||
pnpm build # site statique dans frontend/build/
|
||||
pnpm preview # sert ce build localement
|
||||
```
|
||||
|
||||
## Contributing
|
||||
### Déployer sur un VPS
|
||||
|
||||
Contributions are very welcome — a tool to add, a translation, a correction. **Read [`CONTRIBUTING.md`](./CONTRIBUTING.md) first**: it contains three simple rules for tools (traction, open source, privacy-respecting), a mandatory affiliation-disclosure policy, and a CI-enforced external-link policy. Large changes start with an issue, not a PR.
|
||||
Le build est un dossier de fichiers statiques — n'importe quel serveur web
|
||||
convient. Voir **[`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md)** pour la marche
|
||||
à suivre complète (Caddy avec TLS automatique, nginx, YunoHost, Docker) et
|
||||
la checklist de vérification post-déploiement.
|
||||
|
||||
Not comfortable with code? Open an issue with the app's name, its link, and why it qualifies, and it will be added for you.
|
||||
## Contribuer
|
||||
|
||||
## License
|
||||
Les contributions sont bienvenues — un outil à ajouter, une traduction, une
|
||||
correction. La page **[/contribuer](frontend/src/routes/contribuer/+page.svelte)**
|
||||
du site couvre les trois voies (proposer un outil, corriger une fiche,
|
||||
traduire) ; **[`CONTRIBUTING.md`](CONTRIBUTING.md)** détaille la politique
|
||||
éditoriale (traction, open source, respect de la vie privée, déclaration
|
||||
d'affiliation obligatoire) et **[`docs/CONTENT_GUIDE.md`](docs/CONTENT_GUIDE.md)**
|
||||
le format des fiches et le ton.
|
||||
|
||||
See [`LICENSE`](./LICENSE).
|
||||
Pas à l'aise avec le code ? Ouvre une demande sur
|
||||
[la forge](https://labola.o-k-i.net/cyber-mawonaj/lage-chat-control) avec le
|
||||
nom de l'outil, son lien, et pourquoi il a sa place.
|
||||
|
||||
## Licence
|
||||
|
||||
Voir [`LICENSE`](LICENSE) (MIT).
|
||||
|
||||
---
|
||||
|
||||
_Privacy is a right, not a confession. To encrypt is to vote. To self-host is to disobey. To reclaim your tools is to become ungovernable._
|
||||
_La vie privée est un droit, pas un aveu. Chiffrer, c'est voter. S'auto-héberger, c'est désobéir. Reprendre ses outils, c'est devenir ingouvernable._
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config'
|
||||
import mdx from '@astrojs/mdx'
|
||||
import sitemap from '@astrojs/sitemap'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { defaultLocale, locales } from './src/i18n/locales'
|
||||
|
||||
// Static multilingual guide. `/` is English (canonical); every other
|
||||
// locale in src/i18n/config.ts gets a full prerendered translation at
|
||||
// /<lang>/ — no dual-language DOM, no runtime language CSS toggling.
|
||||
// The whole site must stay readable with JS off.
|
||||
export default defineConfig({
|
||||
site: 'https://exitchatcontrol.org',
|
||||
output: 'static',
|
||||
trailingSlash: 'ignore',
|
||||
build: {
|
||||
// keep every stylesheet external so the CSP can stay `style-src 'self'`
|
||||
// (inline <style> would need per-build hashes)
|
||||
inlineStylesheets: 'never',
|
||||
},
|
||||
i18n: {
|
||||
defaultLocale,
|
||||
locales: [...locales],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
},
|
||||
integrations: [
|
||||
mdx(),
|
||||
sitemap({
|
||||
i18n: {
|
||||
defaultLocale,
|
||||
locales: Object.fromEntries(locales.map((l) => [l, l])),
|
||||
},
|
||||
}),
|
||||
],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"bands": [
|
||||
{
|
||||
"id": "exposed",
|
||||
"min": 0,
|
||||
"max": 24,
|
||||
"title": "Exposé",
|
||||
"summary": "Ta pile est encore très dépendante des grandes plateformes. Les premières étapes sont simples et à fort impact."
|
||||
},
|
||||
{
|
||||
"id": "awakening",
|
||||
"min": 25,
|
||||
"max": 49,
|
||||
"title": "Éveil",
|
||||
"summary": "Tu as commencé à diversifier tes outils. Quelques changements bien choisis feraient une vraie différence."
|
||||
},
|
||||
{
|
||||
"id": "resistant",
|
||||
"min": 50,
|
||||
"max": 74,
|
||||
"title": "Résistant",
|
||||
"summary": "Ta configuration est déjà sérieuse. Tu as une vraie base de résistance à la censure et à la surveillance."
|
||||
},
|
||||
{
|
||||
"id": "hardened",
|
||||
"min": 75,
|
||||
"max": 89,
|
||||
"title": "Durci",
|
||||
"summary": "Tu as une pile plus dure et un socle pseudonyme solide. L'objectif devient la discipline et la séparation des identités."
|
||||
},
|
||||
{
|
||||
"id": "sovereign",
|
||||
"min": 90,
|
||||
"max": 100,
|
||||
"title": "Souverain",
|
||||
"summary": "Ta posture est déjà très robuste. Tu es proche d'une forte souveraineté numérique, avec des choix techniques exigeants."
|
||||
}
|
||||
],
|
||||
"questions": [
|
||||
{
|
||||
"id": "messaging",
|
||||
"category": "messagerie",
|
||||
"label": "Messagerie",
|
||||
"prompt": "Comment échanges-tu le plus souvent avec tes proches ?",
|
||||
"rec": "Bascule tes conversations sensibles vers Signal, SimpleX ou Session.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "WhatsApp / Messenger / SMS", "hint": "Les métadonnées et les plateformes centralisées restent le point faible." },
|
||||
{ "points": 2, "label": "Telegram pour la plupart des conversations", "hint": "Mieux que les grands réseaux, mais la confidentialité reste limitée." },
|
||||
{ "points": 6, "label": "Signal ou Molly pour la plupart des échanges", "hint": "C'est déjà une bonne base pour la résistance à la censure." },
|
||||
{ "points": 10, "label": "SimpleX / Session / Matrix pour les usages sensibles", "hint": "Moins de dépendance au numéro de téléphone et à la plateforme." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "email",
|
||||
"category": "email",
|
||||
"label": "E-mail",
|
||||
"prompt": "Quel service utilises-tu le plus pour tes e-mails ?",
|
||||
"rec": "Utilise Proton Mail ou Tuta, et ajoute PGP pour les contacts qui comptent.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Gmail / Outlook / iCloud Mail", "hint": "Le stockage et l'accès au contenu restent très centralisés." },
|
||||
{ "points": 5, "label": "Proton Mail ou Tuta au quotidien", "hint": "Déjà une option plus sérieuse pour la confidentialité." },
|
||||
{ "points": 7, "label": "Proton / Tuta + PGP pour certains contacts", "hint": "Un bon niveau de résistance si tu es régulier·ère." },
|
||||
{ "points": 10, "label": "PGP / OpenPGP régulier et attention aux métadonnées", "hint": "Une posture beaucoup plus robuste." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "browser",
|
||||
"category": "navigateur",
|
||||
"label": "Navigateur & recherche",
|
||||
"prompt": "Quels navigateur et moteur de recherche utilises-tu le plus ?",
|
||||
"rec": "Quitte Chrome + Google pour un navigateur et un moteur respectueux de la vie privée.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Chrome + Google", "hint": "Confortable, mais fortement traçable." },
|
||||
{ "points": 5, "label": "Firefox ou Brave avec un moteur privé", "hint": "Un vrai pas en avant pour la vie privée." },
|
||||
{ "points": 7, "label": "Brave / Firefox + DuckDuckGo / Startpage / SearXNG", "hint": "C'est déjà une bonne base quotidienne." },
|
||||
{ "points": 10, "label": "Mullvad Browser / LibreWolf / Tor Browser selon le contexte", "hint": "Une posture plus sérieuse face à la surveillance." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dns",
|
||||
"category": "dns",
|
||||
"label": "DNS",
|
||||
"prompt": "Comment résous-tu les noms de domaine ?",
|
||||
"rec": "Passe à un DNS chiffré avec un résolveur minimaliste et respectueux.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Le DNS de ton FAI ou de ta box", "hint": "Le plus simple, mais souvent le moins privé." },
|
||||
{ "points": 4, "label": "Un DNS chiffré chez un fournisseur grand public", "hint": "Un bon premier pas." },
|
||||
{ "points": 7, "label": "Quad9 / Mullvad DNS / NextDNS", "hint": "Une vraie amélioration pour limiter la traçabilité." },
|
||||
{ "points": 10, "label": "DNS chiffré + stratégie anti-tracking assumée", "hint": "Une posture plus consciente et plus robuste." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vpn",
|
||||
"category": "vpn",
|
||||
"label": "VPN",
|
||||
"prompt": "Quel type de VPN utilises-tu ?",
|
||||
"rec": "Choisis un VPN honnête sans journalisation : Mullvad, Proton VPN ou IVPN.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Pas de VPN", "hint": "Le cas le plus exposé pour la traçabilité réseau." },
|
||||
{ "points": 2, "label": "Un VPN gratuit ou douteux", "hint": "Parfois pire que rien si le fournisseur est opaque." },
|
||||
{ "points": 7, "label": "Un VPN honnête, no-log, avec une vraie politique", "hint": "Une vraie amélioration pour la confidentialité." },
|
||||
{ "points": 10, "label": "Mullvad / Proton VPN / IVPN au quotidien", "hint": "Une posture plus sérieuse et cohérente." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "passwords",
|
||||
"category": "motsdepasse",
|
||||
"label": "Mots de passe",
|
||||
"prompt": "Comment gères-tu tes mots de passe ?",
|
||||
"rec": "Adopte un gestionnaire de mots de passe avec des mots de passe uniques et longs.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Mots de passe réutilisés ou stockés dans le navigateur", "hint": "Risque élevé en cas de fuite ou de compromission." },
|
||||
{ "points": 5, "label": "Un gestionnaire de mots de passe basique", "hint": "Déjà un très bon pas en avant." },
|
||||
{ "points": 7, "label": "Bitwarden / KeePassXC / Proton Pass", "hint": "Une posture saine, simple à maintenir." },
|
||||
{ "points": 10, "label": "Gestionnaire + mots de passe uniques et longs", "hint": "Le niveau le plus solide pour limiter les dégâts." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "twofa",
|
||||
"category": "deuxfa",
|
||||
"label": "Double authentification",
|
||||
"prompt": "Quel niveau de double authentification utilises-tu ?",
|
||||
"rec": "Passe la 2FA sur une application, ou mieux, une clé matérielle.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Pas de 2FA", "hint": "Le plus faible niveau de protection des comptes." },
|
||||
{ "points": 2, "label": "SMS ou appel", "hint": "Mieux que rien, mais très fragile." },
|
||||
{ "points": 6, "label": "TOTP sur une application (Aegis / Ente Auth)", "hint": "Une bonne protection au quotidien." },
|
||||
{ "points": 10, "label": "Clé matérielle ou 2FA résistante au phishing", "hint": "Le meilleur niveau de résistance pratique." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "storage",
|
||||
"category": "stockage",
|
||||
"label": "Stockage",
|
||||
"prompt": "Où stockes-tu tes fichiers sensibles ?",
|
||||
"rec": "Déplace tes fichiers sensibles vers Proton Drive, Cryptomator ou un stockage local chiffré.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Google Drive / iCloud / OneDrive", "hint": "Très pratique, mais centralisé." },
|
||||
{ "points": 5, "label": "Proton Drive ou un stockage chiffré de base", "hint": "Une vraie amélioration." },
|
||||
{ "points": 7, "label": "Cryptomator ou un stockage local chiffré", "hint": "Une posture plus robuste." },
|
||||
{ "points": 10, "label": "Stockage local + auto-hébergement / chiffrement avancé", "hint": "Le niveau le plus sérieux pour la souveraineté." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "social",
|
||||
"category": "social",
|
||||
"label": "Réseaux sociaux",
|
||||
"prompt": "Où passes-tu l'essentiel de ta vie sociale en ligne ?",
|
||||
"rec": "Ancre ta présence sur des réseaux décentralisés et réduis la dépendance aux plateformes.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Meta / X / grands réseaux uniquement", "hint": "Le plus simple, mais le plus exposé aux pressions." },
|
||||
{ "points": 5, "label": "Mastodon / Matrix / Nostr de façon ponctuelle", "hint": "Une diversification utile." },
|
||||
{ "points": 7, "label": "Un mix de réseaux décentralisés et de contacts directs", "hint": "Une posture plus indépendante." },
|
||||
{ "points": 10, "label": "Réseaux alternatifs et faible dépendance aux plateformes", "hint": "C'est la vraie logique de résistance." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "os",
|
||||
"category": "os",
|
||||
"label": "Système d'exploitation",
|
||||
"prompt": "Quel système d'exploitation utilises-tu le plus ?",
|
||||
"rec": "Évolue vers GrapheneOS sur mobile et Linux sur l'ordinateur.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Android / iOS / Windows standard", "hint": "Très pratique, mais beaucoup moins respectueux de la vie privée." },
|
||||
{ "points": 5, "label": "Un Android plus privé / Linux / macOS", "hint": "Déjà un bon progrès." },
|
||||
{ "points": 8, "label": "GrapheneOS / Linux / BSD", "hint": "Une base plus sérieuse." },
|
||||
{ "points": 10, "label": "GrapheneOS + Linux + discipline d'installation", "hint": "Déjà une posture très forte." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tor",
|
||||
"category": "tor",
|
||||
"label": "Tor & anonymat",
|
||||
"prompt": "Dans quelle mesure utilises-tu Tor / Tails / l'anonymat ?",
|
||||
"rec": "Prends l'habitude de Tor (et de Tails au besoin) pour tes tâches les plus sensibles.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Jamais ou presque jamais", "hint": "Très exposé si la menace devient sérieuse." },
|
||||
{ "points": 3, "label": "De temps en temps, sans discipline", "hint": "Un premier pas, mais encore fragile." },
|
||||
{ "points": 6, "label": "Tor pour certaines tâches sensibles", "hint": "Déjà plus sérieux." },
|
||||
{ "points": 10, "label": "Tor + Tails avec une discipline forte", "hint": "Le niveau le plus adapté à une menace élevée." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "money",
|
||||
"category": "argent",
|
||||
"label": "Argent",
|
||||
"prompt": "Comment gères-tu ton argent numérique ?",
|
||||
"rec": "Ajoute des moyens de paiement plus privés et sépare tes identités quand c'est possible.",
|
||||
"options": [
|
||||
{ "points": 0, "label": "Cartes bancaires et comptes standards uniquement", "hint": "La traçabilité est forte." },
|
||||
{ "points": 4, "label": "Quelques outils de paiement plus privés", "hint": "Un premier effort de séparation." },
|
||||
{ "points": 6, "label": "Un mix de comptes privés et de crypto limitée", "hint": "Déjà plus réfléchi." },
|
||||
{ "points": 10, "label": "Pratiques de souveraineté financière plus fortes", "hint": "Une posture beaucoup plus sérieuse." }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# Local development only — NOT what production deploys.
|
||||
# docker compose -f docker-compose.dev.yml up dev # HMR on :4321
|
||||
# docker compose -f docker-compose.dev.yml up preview # prod-like on :8080
|
||||
services:
|
||||
dev:
|
||||
image: node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2
|
||||
working_dir: /app
|
||||
command: sh -c "corepack enable && pnpm install --frozen-lockfile && pnpm dev --host"
|
||||
ports:
|
||||
- '4321:4321'
|
||||
volumes:
|
||||
- .:/app
|
||||
- node_modules:/app/node_modules
|
||||
environment:
|
||||
- ASTRO_TELEMETRY_DISABLED=1
|
||||
|
||||
preview:
|
||||
build: .
|
||||
ports:
|
||||
- '8080:80'
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
@@ -1,10 +0,0 @@
|
||||
# Production compose (what Coolify deploys): one service, the static site
|
||||
# built by the multi-stage Dockerfile and served by hardened nginx on port
|
||||
# 80 (EXPOSEd by the Dockerfile). No ports/expose/networks here: the
|
||||
# platform's reverse proxy (Traefik) routes to the container from the
|
||||
# domain assigned in Coolify. For local development use
|
||||
# docker-compose.dev.yml instead.
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
restart: always
|
||||
@@ -1 +0,0 @@
|
||||
add_header Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-APKfv1xaOELs5yz+tPQqjel8CVdobT1z0n9rysaGXPk=' 'sha256-FaYmk1uHh+UNUdWrZIkUBDinPxk/ARkHrA23xUfa/Gk=' 'sha256-8UX5i4mkZDhNhuSxNOdkip2H+FxRXqR708bF3BQPnko=' 'sha256-KU52uW0NoYJbkMOy2EcHza0G3yVB1nScn+wH44Buzr4=' 'sha256-4h+PBVSAvnFY4ecAonGUp9Ne4aHhAuAU8je+PqVl6I4=' 'sha256-J7fub090nAsxTSG8BOZrqblL942lG2lnHuFfzZkky7o=' 'sha256-4hyaOQzqvLr9R6SuXkYh80AxSXJ0K9kC9zbii9v8DGY='; style-src 'self'; img-src 'self' data:; font-src 'self'; manifest-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'" always;
|
||||
@@ -1,27 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
charset utf-8;
|
||||
|
||||
gzip_static on;
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
# hashed build assets: cache forever
|
||||
location /_astro/ {
|
||||
include /etc/nginx/snippets/security-headers.conf;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# everything else (HTML, sitemap, offline artifact): revalidate
|
||||
location / {
|
||||
include /etc/nginx/snippets/security-headers.conf;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
# Shared security headers. nginx's add_header does NOT inherit across
|
||||
# levels once a location adds its own header, so every location includes
|
||||
# this snippet explicitly.
|
||||
include /etc/nginx/snippets/csp.conf;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||
@@ -54,8 +54,7 @@ réviser une fiche, relis-en deux. Ce qui change par rapport au ton d'origine :
|
||||
tutoiement systématique, le geste avant la technique (« dix secondes, effet
|
||||
immédiat »), l'enjeu politique dit sans détour (« ta correspondance — pas une
|
||||
matière première publicitaire »), et un ancrage collectif (« commence par ton
|
||||
groupe de famille »). Le script de migration ne régénère jamais une fiche
|
||||
existante (`--force` pour tout écraser) : les réécritures sont protégées.
|
||||
groupe de famille »).
|
||||
|
||||
## Créole guadeloupéen (gcf)
|
||||
|
||||
|
||||
+16
-4
@@ -125,10 +125,22 @@ YunoHost gère TLS, HSTS et le renouvellement des certificats.
|
||||
|
||||
## 6. Option D — Docker
|
||||
|
||||
Le `Dockerfile` et `docker/nginx.conf` du dépôt (hérités du site Astro)
|
||||
servent déjà un site statique durci ; adapter le chemin de copie vers
|
||||
`frontend/build/` si tu veux cette voie. Utile pour l'infra OKI si tout
|
||||
le reste tourne déjà en conteneurs — sinon les options A/B sont plus simples.
|
||||
Le dépôt n'embarque volontairement aucun `Dockerfile` (un site 100 % statique
|
||||
n'en a pas besoin — c'est un dossier de fichiers). Si le reste de l'infra OKI
|
||||
tourne déjà en conteneurs, ce `Dockerfile` minimal (nginx durci, sans build
|
||||
step) suffit :
|
||||
|
||||
```dockerfile
|
||||
FROM nginx:alpine
|
||||
COPY frontend/build/ /usr/share/nginx/html/
|
||||
COPY docs/nginx.conf.example /etc/nginx/conf.d/default.conf
|
||||
```
|
||||
|
||||
Reprends le bloc `server {}` de l'option B ci-dessus dans
|
||||
`docs/nginx.conf.example` (à créer), en retirant `server_name`/`certbot`
|
||||
(le TLS se termine en amont, sur un reverse proxy ou le load balancer).
|
||||
Sinon, les options A/B sont plus simples et suffisent dans l'immense
|
||||
majorité des cas.
|
||||
|
||||
## 7. Vérifier après mise en ligne
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Design system OKI — exitchatcontrol.org
|
||||
# Design system OKI — Lagé Chat Control
|
||||
|
||||
Toutes les valeurs visuelles vivent dans
|
||||
[`frontend/src/styles/tokens.css`](../frontend/src/styles/tokens.css).
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// @ts-check
|
||||
import js from '@eslint/js'
|
||||
import astro from 'eslint-plugin-astro'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist/', '.astro/', 'node_modules/', 'playwright-report/', 'test-results/'] },
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...astro.configs.recommended,
|
||||
{
|
||||
files: ['scripts/**/*.mjs', 'migration/**/*.mjs'],
|
||||
languageOptions: {
|
||||
globals: { ...globals.node },
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.{ts,astro}'],
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser },
|
||||
},
|
||||
},
|
||||
)
|
||||
+22
-13
@@ -1,4 +1,4 @@
|
||||
# exitchatcontrol × OKI — frontend
|
||||
# Lagé Chat Control × OKI — frontend
|
||||
|
||||
Annuaire souverain d'outils libres. SvelteKit 2 + Svelte 5, site 100 %
|
||||
prérendu, zéro CDN tiers, zéro tracker. Voir [`ADR.md`](../ADR.md) pour la
|
||||
@@ -13,18 +13,19 @@ Node ≥ 22.12, pnpm.
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm migrate # (re)génère content/ depuis l'ancien site Astro (src/)
|
||||
pnpm dev # serveur de développement
|
||||
pnpm build # build statique dans build/ (prérend les 58 pages)
|
||||
pnpm preview # sert le build localement
|
||||
pnpm check # svelte-check (types + a11y)
|
||||
pnpm dev # serveur de développement
|
||||
pnpm build # build statique dans build/ (prérend toutes les pages)
|
||||
pnpm preview # sert le build localement
|
||||
pnpm check # svelte-check (types + a11y)
|
||||
pnpm audit:a11y # audit axe-core sur le build servi en local
|
||||
```
|
||||
|
||||
## Déploiement
|
||||
|
||||
`build/` est un site statique : n'importe quel serveur de fichiers convient
|
||||
(nginx du `docker/` existant, YunoHost `my_webapp`, etc.). Aucune dépendance
|
||||
cloud, aucun service à maintenir — un miroir = une copie du dossier.
|
||||
`build/` est un site statique : n'importe quel serveur de fichiers convient.
|
||||
Voir [`docs/DEPLOYMENT.md`](../docs/DEPLOYMENT.md) pour la mise en production
|
||||
sur un VPS (Caddy, nginx, YunoHost, Docker). Aucune dépendance cloud, aucun
|
||||
service à maintenir — un miroir = une copie du dossier.
|
||||
|
||||
## Arborescence
|
||||
|
||||
@@ -32,9 +33,17 @@ cloud, aucun service à maintenir — un miroir = une copie du dossier.
|
||||
src/styles/ tokens, reset, base, layouts (cascade @layer)
|
||||
src/lib/i18n/ fr.json + gcf.json typés, store de langue (runes)
|
||||
src/lib/components/ ToolCard, DifficultyBadge, CategoryNav, SearchBar,
|
||||
LanguageSwitcher, EndMarker
|
||||
src/lib/content/ chargeur build-time des fichiers ../content/
|
||||
src/routes/ / (landing), /outils (annuaire filtrable), /outils/[slug]
|
||||
LanguageSwitcher, EndMarker, ShortcutsDialog,
|
||||
OfflineBadge, AdoptButton
|
||||
src/lib/content/ chargeurs build-time des fichiers ../content/
|
||||
(tools.server.ts, quiz.server.ts)
|
||||
src/routes/ / (landing), /outils (annuaire filtrable),
|
||||
/outils/[slug], /quiz, /contribuer
|
||||
src/service-worker.js précache + offline-first
|
||||
static/fonts/ 2 WOFF2 auto-hébergés (Atkinson Hyperlegible, Fraunces)
|
||||
scripts/ migrate-tools.mjs
|
||||
scripts/audit-a11y.mjs audit axe-core (pnpm audit:a11y)
|
||||
```
|
||||
|
||||
Le contenu éditorial (fiches d'outils, quiz, catégories) vit dans
|
||||
[`../content/`](../content/) à la racine du dépôt, pas dans `frontend/` —
|
||||
voir [`docs/CONTENT_GUIDE.md`](../docs/CONTENT_GUIDE.md) pour l'éditer.
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"name": "exitchatcontrol-oki",
|
||||
"name": "lage-chat-control",
|
||||
"private": true,
|
||||
"version": "4.0.0-alpha.1",
|
||||
"type": "module",
|
||||
"description": "Exit Chat Control × OKI — annuaire souverain d'outils libres. SvelteKit statique, zéro tracker, zéro CDN.",
|
||||
"description": "Lagé Chat Control × OKI — annuaire souverain d'outils libres. SvelteKit statique, zéro tracker, zéro CDN.",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"migrate": "node scripts/migrate-tools.mjs",
|
||||
"audit:a11y": "node scripts/audit-a11y.mjs"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
@@ -2,7 +2,7 @@
|
||||
import { chromium } from '@playwright/test'
|
||||
import { AxeBuilder } from '@axe-core/playwright'
|
||||
|
||||
const PAGES = ['/', '/outils', '/outils/signal', '/outils/tor', '/contribuer']
|
||||
const PAGES = ['/', '/outils', '/outils/signal', '/outils/tor', '/contribuer', '/quiz']
|
||||
const browser = await chromium.launch()
|
||||
const context = await browser.newContext()
|
||||
const page = await context.newPage()
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* Migration des données du site Astro vers les fichiers plats OKI (ADR-001,
|
||||
* mission §7). Extrait les 55 ToolCard des MDX français + métadonnées de
|
||||
* src/data/{tools,directory}.json et produit :
|
||||
* content/outils/<slug>.md (frontmatter JSON-par-ligne + sections HTML)
|
||||
* content/categories.json (catégories dérivées des sections sources)
|
||||
*
|
||||
* Idempotent : relançable, écrase la sortie. Usage : pnpm migrate
|
||||
*/
|
||||
import { readFile, readdir, writeFile, mkdir } from 'node:fs/promises'
|
||||
import { existsSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
||||
const SRC = path.join(ROOT, 'src')
|
||||
const OUT = path.join(ROOT, 'content')
|
||||
|
||||
const DIFFICULTY = { 1: 'beginner', 2: 'intermediate', 3: 'advanced' }
|
||||
|
||||
const tools = JSON.parse(await readFile(path.join(SRC, 'data', 'tools.json'), 'utf8'))
|
||||
const directory = JSON.parse(await readFile(path.join(SRC, 'data', 'directory.json'), 'utf8'))
|
||||
const toolById = new Map(tools.map((t) => [t.id, t]))
|
||||
const dirById = new Map(directory.map((d) => [d.id, d]))
|
||||
|
||||
function parseFrontmatter(raw) {
|
||||
const m = raw.match(/^---\n([\s\S]*?)\n---\n?/)
|
||||
if (!m) return [{}, raw]
|
||||
const meta = {}
|
||||
for (const line of m[1].split('\n')) {
|
||||
const i = line.indexOf(':')
|
||||
if (i === -1) continue
|
||||
const key = line.slice(0, i).trim()
|
||||
let value = line.slice(i + 1).trim()
|
||||
if (/^'.*'$/.test(value) || /^".*"$/.test(value)) value = value.slice(1, -1)
|
||||
meta[key] = /^\d+$/.test(value) ? Number(value) : value
|
||||
}
|
||||
return [meta, raw.slice(m[0].length)]
|
||||
}
|
||||
|
||||
/** Extrait le contenu d'un slot (Fragment/ol/ul) dans un bloc ToolCard. */
|
||||
function slot(block, name) {
|
||||
const re = new RegExp(
|
||||
`<(Fragment|ol|ul)([^>]*slot="${name}"[^>]*)>([\\s\\S]*?)</\\1>`
|
||||
)
|
||||
const m = block.match(re)
|
||||
if (!m) return null
|
||||
// Les listes gardent leur balise (le HTML passe tel quel dans le markdown) ;
|
||||
// les Fragments sont déballés.
|
||||
return m[1] === 'Fragment' ? m[3].trim() : `<${m[1]}>${m[3]}</${m[1]}>`.trim()
|
||||
}
|
||||
|
||||
/** Tags = textes des <span class="tool-tag"> hors difficulté (déjà structurée). */
|
||||
function extractTags(block) {
|
||||
const html = slot(block, 'tags') ?? ''
|
||||
const tags = [...html.matchAll(/<span class="tool-tag">([\s\S]*?)<\/span>/g)]
|
||||
.map((m) => m[1].replace(/\s+/g, ' ').trim())
|
||||
.filter((t) => !/^[🟢🟡🔴]/u.test(t))
|
||||
return tags
|
||||
}
|
||||
|
||||
/** Difficulté : `level` de tools.json, sinon l'emoji du tag MDX (🟢/🟡/🔴). */
|
||||
function difficulty(tool, block) {
|
||||
if (DIFFICULTY[tool.level]) return DIFFICULTY[tool.level]
|
||||
const html = slot(block, 'tags') ?? ''
|
||||
if (html.includes('🟢')) return 'beginner'
|
||||
if (html.includes('🟡')) return 'intermediate'
|
||||
if (html.includes('🔴')) return 'advanced'
|
||||
console.warn(`⚠ ${tool.id} : difficulté introuvable — « intermediate » par défaut`)
|
||||
return 'intermediate'
|
||||
}
|
||||
|
||||
const fm = (key, value) => (value == null ? null : `${key}: ${JSON.stringify(value)}`)
|
||||
|
||||
const sectionsDir = path.join(SRC, 'content', 'sections', 'fr')
|
||||
const files = (await readdir(sectionsDir)).filter((f) => f.endsWith('.mdx')).sort()
|
||||
|
||||
await mkdir(path.join(OUT, 'outils'), { recursive: true })
|
||||
|
||||
const categories = []
|
||||
const seen = new Set()
|
||||
let written = 0
|
||||
|
||||
for (const file of files) {
|
||||
const raw = await readFile(path.join(sectionsDir, file), 'utf8')
|
||||
const [meta, body] = parseFrontmatter(raw)
|
||||
const blocks = [...body.matchAll(/<ToolCard tool="([^"]+)"[^>]*>([\s\S]*?)<\/ToolCard>/g)]
|
||||
if (blocks.length === 0) continue
|
||||
|
||||
categories.push({
|
||||
slug: meta.id,
|
||||
title: meta.title,
|
||||
order: meta.order,
|
||||
part: meta.part
|
||||
})
|
||||
|
||||
for (const [, toolId, block] of blocks) {
|
||||
const tool = toolById.get(toolId)
|
||||
if (!tool) {
|
||||
console.warn(`⚠ ${file} : ToolCard "${toolId}" absent de tools.json — ignoré`)
|
||||
continue
|
||||
}
|
||||
const slug = toolId.replace(/^t-/, '')
|
||||
if (seen.has(slug)) {
|
||||
console.warn(`⚠ ${slug} : doublon (déjà émis) — ignoré dans ${file}`)
|
||||
continue
|
||||
}
|
||||
seen.add(slug)
|
||||
// ne jamais écraser une fiche retravaillée à la main (--force pour tout régénérer)
|
||||
const outPath = path.join(OUT, 'outils', `${slug}.md`)
|
||||
if (existsSync(outPath) && !process.argv.includes('--force')) {
|
||||
written++
|
||||
continue
|
||||
}
|
||||
const dir = dirById.get(slug)
|
||||
const nameOverride = slot(block, 'name')
|
||||
|
||||
const front = [
|
||||
fm('name', nameOverride ?? tool.name),
|
||||
fm('slug', slug),
|
||||
fm('category', meta.id),
|
||||
fm('difficulty', difficulty(tool, block)),
|
||||
fm('license', dir?.license),
|
||||
fm('url', tool.url),
|
||||
fm('color', tool.color),
|
||||
fm('monogram', tool.monogram),
|
||||
fm('iconSlug', tool.iconSlug),
|
||||
fm('tags', extractTags(block)),
|
||||
fm('lang', 'fr')
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
|
||||
const sections = [
|
||||
['what', slot(block, 'what')],
|
||||
['why', slot(block, 'why')],
|
||||
['who', slot(block, 'who')],
|
||||
['install', slot(block, 'install')],
|
||||
['extra', slot(block, 'extra')]
|
||||
]
|
||||
.filter(([, html]) => html)
|
||||
.map(([key, html]) => `## ${key}\n\n${html}`)
|
||||
.join('\n\n')
|
||||
|
||||
await writeFile(outPath, `---\n${front}\n---\n\n${sections}\n`)
|
||||
written++
|
||||
}
|
||||
}
|
||||
|
||||
await writeFile(path.join(OUT, 'categories.json'), JSON.stringify(categories, null, 2) + '\n')
|
||||
console.log(`✔ ${written} fiches écrites dans content/outils/, ${categories.length} catégories`)
|
||||
|
||||
const missing = tools.filter((t) => !seen.has(t.id.replace(/^t-/, '')))
|
||||
if (missing.length) {
|
||||
console.warn(`⚠ outils de tools.json sans ToolCard fr : ${missing.map((t) => t.id).join(', ')}`)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
export interface QuizOption {
|
||||
points: number
|
||||
label: string
|
||||
hint: string
|
||||
}
|
||||
|
||||
export interface QuizQuestion {
|
||||
id: string
|
||||
/** slug de content/categories.json — relie chaque question à l'annuaire */
|
||||
category: string
|
||||
label: string
|
||||
prompt: string
|
||||
rec: string
|
||||
options: QuizOption[]
|
||||
}
|
||||
|
||||
export interface QuizBand {
|
||||
id: string
|
||||
min: number
|
||||
max: number
|
||||
title: string
|
||||
summary: string
|
||||
}
|
||||
|
||||
export interface QuizContent {
|
||||
bands: QuizBand[]
|
||||
questions: QuizQuestion[]
|
||||
}
|
||||
|
||||
const CONTENT = path.resolve(process.cwd(), '..', 'content')
|
||||
|
||||
let cache: QuizContent | null = null
|
||||
|
||||
export function loadQuiz(): QuizContent {
|
||||
if (!cache) {
|
||||
cache = JSON.parse(readFileSync(path.join(CONTENT, 'quiz.json'), 'utf8')) as QuizContent
|
||||
}
|
||||
return cache
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"site": {
|
||||
"name": "Exit Chat Control",
|
||||
"name": "Lagé Chat Control",
|
||||
"tagline": "Reprends le contrôle de ton internet",
|
||||
"description": "Annuaire d'outils libres pour la souveraineté numérique — messagerie chiffrée, VPN, auto-hébergement, OS libres."
|
||||
},
|
||||
@@ -111,5 +111,27 @@
|
||||
"footer": {
|
||||
"license": "Contenu et code libres (MIT). Zéro tracker, zéro CDN, zéro cookie.",
|
||||
"contribute": "Proposer un outil ou une correction"
|
||||
},
|
||||
"quiz": {
|
||||
"nav": "Quiz",
|
||||
"eyebrow": "Évaluer sa résistance à la censure",
|
||||
"title": "Quiz de résistance",
|
||||
"lede": "Une boussole, pas un classement : réponds à douze questions rapides pour repérer les points faibles de ta pile — sans jugement — puis suis des pistes concrètes vers l'annuaire.",
|
||||
"privacyNote": "Le score est calculé localement dans ton navigateur. Rien n'est envoyé, rien n'est enregistré.",
|
||||
"start": "Commencer le quiz",
|
||||
"readDirectory": "Voir l'annuaire",
|
||||
"progress": "{done} / {total} répondues",
|
||||
"resultEyebrow": "Ton résultat",
|
||||
"outOf": "/ 100",
|
||||
"barsHeading": "Score par domaine",
|
||||
"recHeading": "Tes 3 prochaines étapes",
|
||||
"openDirectory": "Voir les outils →",
|
||||
"restart": "Refaire le quiz",
|
||||
"copy": "Copier le lien du résultat",
|
||||
"copied": "Lien copié ✓",
|
||||
"share": "Partager",
|
||||
"shareText": "Mon score de résistance à la censure : {score}/100 — {band}. Évalue le tien :",
|
||||
"bandsHeading": "Ce que le score signifie",
|
||||
"categoriesHeading": "Les douze domaines, et où progresser"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"site": {
|
||||
"name": "Exit Chat Control",
|
||||
"name": "Lagé Chat Control",
|
||||
"tagline": "Pran kontwòl entènèt a-w",
|
||||
"description": "Lis a zouti lib pou libèté niméwik — mésajri chifré, VPN, oto-ébèjman, sistèm lib."
|
||||
},
|
||||
@@ -111,5 +111,27 @@
|
||||
"footer": {
|
||||
"license": "Kontni é kòd lib (MIT). Pon trakè, pon CDN, pon cookie.",
|
||||
"contribute": "Pwopozé on zouti oben on koreksyon"
|
||||
},
|
||||
"quiz": {
|
||||
"nav": "Quiz",
|
||||
"eyebrow": "Èvalyé rézistans a-w a sansi-la",
|
||||
"title": "Quiz rézistans",
|
||||
"lede": "On bousòl, pa on klasman : réponn douz kèsyon rapid pou trapé pwen fèb an pil a-w — san jijman — apré swiv chimen konkrè vè lis a zouti-la.",
|
||||
"privacyNote": "Skò-la ka kalkilé lokalman an navigatè a-w. Ayen pa voyé, ayen pa anrèjistré.",
|
||||
"start": "Koumansé quiz-la",
|
||||
"readDirectory": "Gadé lis-la",
|
||||
"progress": "{done} / {total} réponn",
|
||||
"resultEyebrow": "Rézilta a-w",
|
||||
"outOf": "/ 100",
|
||||
"barsHeading": "Skò pa domèn",
|
||||
"recHeading": "Twa pwochen étap a-w",
|
||||
"openDirectory": "Gadé zouti yo →",
|
||||
"restart": "Rifè quiz-la",
|
||||
"copy": "Kopyé lyen rézilta-la",
|
||||
"copied": "Lyen kopyé ✓",
|
||||
"share": "Pataj",
|
||||
"shareText": "Skò rézistans a sansi a mwen : {score}/100 — {band}. Èvalyé pa a-w :",
|
||||
"bandsHeading": "Sa skò-la vlé di",
|
||||
"categoriesHeading": "Douz domèn yo, é kotè pou pwogrésé"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<nav aria-label={t('nav.home')}>
|
||||
<ul class="cluster">
|
||||
<li><a href="/outils">{t('nav.directory')}</a></li>
|
||||
<li><a href="/quiz">{t('quiz.nav')}</a></li>
|
||||
<li><a href="/contribuer">{t('nav.contribute')}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { loadQuiz } from '$lib/content/quiz.server'
|
||||
|
||||
export function load() {
|
||||
return loadQuiz()
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/i18n/index.svelte'
|
||||
import type { QuizContent } from '$lib/content/quiz.server'
|
||||
|
||||
let { data }: { data: QuizContent } = $props()
|
||||
// le contenu du quiz est figé pour la durée de vie de la page (pas de
|
||||
// navigation interne qui le changerait) — une seule capture au montage
|
||||
const { bands, questions } = $state.snapshot(data)
|
||||
|
||||
const STORAGE_KEY = 'oki-quiz'
|
||||
|
||||
function readSaved(): Record<string, number> {
|
||||
if (typeof localStorage === 'undefined') return {}
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}') as Record<
|
||||
string,
|
||||
unknown
|
||||
>
|
||||
const out: Record<string, number> = {}
|
||||
for (const [k, v] of Object.entries(parsed)) if (typeof v === 'number') out[k] = v
|
||||
return out
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// Un seul état source : { questionId: points }. Persisté en local
|
||||
// uniquement (doctrine §4.3 privacyNote) — jamais envoyé nulle part.
|
||||
let answers = $state<Record<string, number>>(readSaved())
|
||||
|
||||
$effect(() => {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(answers))
|
||||
} catch {
|
||||
/* mode privé : le quiz fonctionne quand même pour cette visite */
|
||||
}
|
||||
})
|
||||
|
||||
let answeredCount = $derived(Object.keys(answers).length)
|
||||
let total = questions.length
|
||||
let complete = $derived(answeredCount === total)
|
||||
|
||||
let totalPoints = $derived(Object.values(answers).reduce((s, p) => s + p, 0))
|
||||
let maxPoints = $derived(
|
||||
questions.reduce((s, q) => s + Math.max(...q.options.map((o) => o.points)), 0)
|
||||
)
|
||||
let score = $derived(maxPoints > 0 ? Math.round((totalPoints / maxPoints) * 100) : 0)
|
||||
let band = $derived(bands.find((b) => score >= b.min && score <= b.max) ?? bands[0])
|
||||
|
||||
interface Ratio {
|
||||
question: (typeof questions)[number]
|
||||
points: number
|
||||
max: number
|
||||
ratio: number
|
||||
}
|
||||
|
||||
let ratios = $derived<Ratio[]>(
|
||||
questions.map((q) => {
|
||||
const max = Math.max(...q.options.map((o) => o.points))
|
||||
const points = answers[q.id] ?? 0
|
||||
return { question: q, points, max, ratio: max > 0 ? points / max : 0 }
|
||||
})
|
||||
)
|
||||
|
||||
let weakest = $derived([...ratios].sort((a, b) => a.ratio - b.ratio).slice(0, 3))
|
||||
|
||||
let resultPanel: HTMLElement | undefined = $state()
|
||||
let wasComplete = false
|
||||
$effect(() => {
|
||||
if (complete && !wasComplete) resultPanel?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
wasComplete = complete
|
||||
})
|
||||
|
||||
function restart() {
|
||||
answers = {}
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
} catch {
|
||||
/* rien à effacer */
|
||||
}
|
||||
document.getElementById('quiz-form')?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
|
||||
let copied = $state(false)
|
||||
function shareUrl() {
|
||||
return location.href.replace(/[?#].*$/, '')
|
||||
}
|
||||
function shareMessage() {
|
||||
return t('quiz.shareText').replace('{score}', String(score)).replace('{band}', band.title)
|
||||
}
|
||||
async function copyLink() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(`${shareMessage()} ${shareUrl()}`)
|
||||
copied = true
|
||||
setTimeout(() => (copied = false), 2000)
|
||||
} catch {
|
||||
/* presse-papier indisponible */
|
||||
}
|
||||
}
|
||||
let canNativeShare = $derived(typeof navigator !== 'undefined' && 'share' in navigator)
|
||||
async function nativeShare() {
|
||||
try {
|
||||
await navigator.share({ title: t('quiz.title'), text: shareMessage(), url: shareUrl() })
|
||||
} catch {
|
||||
/* annulé ou non supporté */
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{t('quiz.title')} — {t('site.name')}</title>
|
||||
<meta name="description" content={t('quiz.lede')} />
|
||||
</svelte:head>
|
||||
|
||||
<header class="stack">
|
||||
<p class="eyebrow">{t('quiz.eyebrow')}</p>
|
||||
<h1>{t('quiz.title')}</h1>
|
||||
<p class="prose">{t('quiz.lede')}</p>
|
||||
<p class="privacy">{t('quiz.privacyNote')}</p>
|
||||
<p class="cluster">
|
||||
<a class="cta" href="#quiz-form">{t('quiz.start')}</a>
|
||||
<a class="secondary" href="/outils">{t('quiz.readDirectory')}</a>
|
||||
</p>
|
||||
<noscript>
|
||||
<p class="privacy">{t('quiz.privacyNote')} — JavaScript est requis pour calculer ton score.</p>
|
||||
</noscript>
|
||||
</header>
|
||||
|
||||
{#if answeredCount > 0}
|
||||
<!-- point d'arrêt naturel : la progression est visible, jamais un
|
||||
chargement automatique en arrière-plan (doctrine §1.5) -->
|
||||
<div class="progress-block" role="status">
|
||||
<p class="progress-text">{t('quiz.progress').replace('{done}', String(answeredCount)).replace('{total}', String(total))}</p>
|
||||
<div class="progress-track" aria-hidden="true">
|
||||
<div class="progress-fill" style:--ratio="{(answeredCount / total) * 100}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form id="quiz-form" class="stack" style="--gap: var(--space-4)">
|
||||
<ol class="questions stack" style="--gap: var(--space-4)">
|
||||
{#each questions as question, i (question.id)}
|
||||
<li>
|
||||
<fieldset class="question stack">
|
||||
<legend>
|
||||
<span class="qnum" aria-hidden="true">{String(i + 1).padStart(2, '0')}</span>
|
||||
{question.prompt}
|
||||
</legend>
|
||||
<div class="options">
|
||||
{#each question.options as option (option.label)}
|
||||
<label class="option" class:checked={answers[question.id] === option.points}>
|
||||
<input
|
||||
type="radio"
|
||||
name={question.id}
|
||||
value={option.points}
|
||||
checked={answers[question.id] === option.points}
|
||||
onchange={() => (answers = { ...answers, [question.id]: option.points })}
|
||||
/>
|
||||
<span class="option-body">
|
||||
<span class="option-label">{option.label}</span>
|
||||
<span class="option-hint">{option.hint}</span>
|
||||
</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</fieldset>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</form>
|
||||
|
||||
{#if complete}
|
||||
<section class="result stack" bind:this={resultPanel} tabindex="-1" aria-live="polite">
|
||||
<p class="eyebrow">{t('quiz.resultEyebrow')}</p>
|
||||
<p class="score"><strong>{score}</strong><span>{t('quiz.outOf')}</span></p>
|
||||
<p class="band-title">{band.title}</p>
|
||||
<p class="prose">{band.summary}</p>
|
||||
|
||||
<h2>{t('quiz.barsHeading')}</h2>
|
||||
<div class="bars stack" style="--gap: var(--space-2)">
|
||||
{#each ratios as r (r.question.id)}
|
||||
<div class="bar-row">
|
||||
<span class="bar-label">{r.question.label}</span>
|
||||
<div class="bar-track">
|
||||
<div class="bar-fill" style:--ratio="{r.ratio * 100}%"></div>
|
||||
</div>
|
||||
<strong class="bar-value">{r.points}/{r.max}</strong>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<h2>{t('quiz.recHeading')}</h2>
|
||||
<div class="card-grid">
|
||||
<ul>
|
||||
{#each weakest as r (r.question.id)}
|
||||
<li class="rec-card stack">
|
||||
<strong>{r.question.label}</strong>
|
||||
<p class="prose">{r.question.rec}</p>
|
||||
<a href="/outils?cat={r.question.category}">{t('quiz.openDirectory')}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="cluster actions">
|
||||
<button type="button" onclick={copyLink}>{copied ? t('quiz.copied') : t('quiz.copy')}</button>
|
||||
{#if canNativeShare}
|
||||
<button type="button" onclick={nativeShare}>{t('quiz.share')}</button>
|
||||
{/if}
|
||||
<button type="button" class="restart" onclick={restart}>{t('quiz.restart')}</button>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="key stack">
|
||||
<h2>{t('quiz.bandsHeading')}</h2>
|
||||
<ul class="band-key">
|
||||
{#each bands as b (b.id)}
|
||||
<li>
|
||||
<strong>{b.title}</strong>
|
||||
<span class="range">{b.min}–{b.max}</span>
|
||||
<p class="prose">{b.summary}</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<h2>{t('quiz.categoriesHeading')}</h2>
|
||||
<div class="card-grid">
|
||||
<ul>
|
||||
{#each questions as q (q.id)}
|
||||
<li class="cat-card stack">
|
||||
<strong>{q.label}</strong>
|
||||
<p class="prose">{q.rec}</p>
|
||||
<a href="/outils?cat={q.category}">{t('quiz.openDirectory')}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.eyebrow {
|
||||
font-family: var(--font-text);
|
||||
font-size: var(--text-0);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
|
||||
.privacy {
|
||||
font-size: var(--text-0);
|
||||
color: var(--oki-ink-2);
|
||||
}
|
||||
|
||||
.cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-4);
|
||||
background: var(--oki-jaune);
|
||||
color: var(--oki-noir);
|
||||
font-weight: 700;
|
||||
border-radius: var(--radius-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-4);
|
||||
border: 1px solid var(--oki-line);
|
||||
border-radius: var(--radius-2);
|
||||
text-decoration: none;
|
||||
color: var(--oki-ink);
|
||||
}
|
||||
|
||||
/* ── progression ──────────────────────────────────────────────── */
|
||||
.progress-block {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
background: color-mix(in oklch, var(--oki-surface) 92%, transparent);
|
||||
backdrop-filter: blur(4px);
|
||||
padding-block: var(--space-2);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: var(--text-0);
|
||||
color: var(--oki-ink-2);
|
||||
margin-block-end: var(--space-1);
|
||||
}
|
||||
|
||||
.progress-track,
|
||||
.bar-track {
|
||||
inline-size: 100%;
|
||||
block-size: 0.5rem;
|
||||
background: var(--oki-surface-2);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill,
|
||||
.bar-fill {
|
||||
inline-size: var(--ratio, 0%);
|
||||
block-size: 100%;
|
||||
background: var(--oki-jaune);
|
||||
border-radius: inherit;
|
||||
transition: inline-size var(--duration-2) var(--ease-out);
|
||||
}
|
||||
|
||||
/* ── questions ────────────────────────────────────────────────── */
|
||||
.question {
|
||||
border: 1px solid var(--oki-line);
|
||||
border-radius: var(--radius-2);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
legend {
|
||||
font-weight: 700;
|
||||
padding-inline: var(--space-1);
|
||||
}
|
||||
|
||||
.qnum {
|
||||
color: var(--accent-ink);
|
||||
font-family: var(--font-display);
|
||||
margin-inline-end: var(--space-2);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
grid-template-columns: 1fr;
|
||||
margin-block-start: var(--space-3);
|
||||
}
|
||||
|
||||
@container (min-width: 44rem) {
|
||||
.options {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-2);
|
||||
min-height: var(--tap-target);
|
||||
padding: var(--space-3);
|
||||
border: 1px solid var(--oki-line);
|
||||
border-radius: var(--radius-2);
|
||||
background: var(--oki-surface-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.option.checked {
|
||||
border-color: var(--oki-jaune);
|
||||
background: var(--oki-surface);
|
||||
outline: 2px solid var(--oki-jaune);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.option input {
|
||||
margin-block-start: 0.2em;
|
||||
inline-size: 1.2rem;
|
||||
block-size: 1.2rem;
|
||||
accent-color: var(--oki-jaune);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.option-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-weight: 700;
|
||||
font-size: var(--text-0);
|
||||
}
|
||||
|
||||
.option-hint {
|
||||
font-size: var(--text-0);
|
||||
color: var(--oki-ink-2);
|
||||
}
|
||||
|
||||
/* ── résultat ──────────────────────────────────────────────────── */
|
||||
.result {
|
||||
border: 1px solid var(--oki-line);
|
||||
border-inline-start: 4px solid var(--oki-jaune);
|
||||
border-radius: var(--radius-2);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.score {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-1);
|
||||
font-family: var(--font-display);
|
||||
}
|
||||
|
||||
.score strong {
|
||||
font-size: var(--text-5);
|
||||
}
|
||||
|
||||
.score span {
|
||||
color: var(--oki-ink-2);
|
||||
font-size: var(--text-2);
|
||||
}
|
||||
|
||||
.band-title {
|
||||
font-family: var(--font-text);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
|
||||
.bar-row {
|
||||
display: grid;
|
||||
grid-template-columns: 9rem 1fr 3.5rem;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: var(--text-0);
|
||||
color: var(--oki-ink-2);
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
font-size: var(--text-0);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.rec-card,
|
||||
.cat-card {
|
||||
padding: var(--space-3);
|
||||
border: 1px solid var(--oki-line);
|
||||
border-radius: var(--radius-2);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
border-top: 1px solid var(--oki-line);
|
||||
padding-block-start: var(--space-3);
|
||||
}
|
||||
|
||||
.actions button {
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-3);
|
||||
}
|
||||
|
||||
.actions .restart {
|
||||
margin-inline-start: auto;
|
||||
background: none;
|
||||
border-color: transparent;
|
||||
color: var(--oki-ink-2);
|
||||
}
|
||||
|
||||
/* ── clé de lecture ────────────────────────────────────────────── */
|
||||
.key {
|
||||
border-top: 1px solid var(--oki-line);
|
||||
padding-block-start: var(--space-5);
|
||||
}
|
||||
|
||||
.band-key {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.band-key li {
|
||||
padding: var(--space-3);
|
||||
border: 1px solid var(--oki-line);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
.band-key strong {
|
||||
font-family: var(--font-display);
|
||||
}
|
||||
|
||||
.range {
|
||||
color: var(--oki-ink-2);
|
||||
font-size: var(--text-0);
|
||||
margin-inline-start: var(--space-2);
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@
|
||||
import { build, files, version } from '$service-worker'
|
||||
|
||||
const CACHE = `oki-${version}`
|
||||
const SHELL = ['/', '/outils', '/contribuer', ...build, ...files]
|
||||
const SHELL = ['/', '/outils', '/quiz', '/contribuer', ...build, ...files]
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Contact: https://labola.o-k-i.net/cyber-mawonaj/lage-chat-control/issues
|
||||
Preferred-Languages: fr, gcf
|
||||
Expires: 2027-07-01T00:00:00.000Z
|
||||
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Exit Chat Control",
|
||||
"short_name": "ExitCC",
|
||||
"name": "Lagé Chat Control",
|
||||
"short_name": "Lagé CC",
|
||||
"description": "Annuaire d'outils libres pour la souveraineté numérique",
|
||||
"lang": "fr",
|
||||
"start_url": "/",
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
// QA gate for the content migration: proves that, inside the narrative window
|
||||
// (everything between the first section head and the manifesto), the built
|
||||
// pages carry EXACTLY the legacy prose, line-for-line and in order, modulo
|
||||
// the documented categories below. Anything else fails loudly.
|
||||
//
|
||||
// Categories (each is reported with counts and samples):
|
||||
// labels built-only lines that are ToolCard field labels — in the
|
||||
// legacy page these strings were CSS-generated (::before
|
||||
// content), so they are absent from the legacy DOM text.
|
||||
// excluded legacy-only lines that belong to the deliberately deferred
|
||||
// legislative-status claims (see extract-log.json).
|
||||
// quotes " and ' are typeset as “ ” ‘ ’ by Astro's default
|
||||
// markdown `smartypants`; normalized before comparing.
|
||||
// spacing identical text but different whitespace at tag boundaries
|
||||
// (component templates put line breaks between inline elements
|
||||
// where the legacy HTML had none).
|
||||
//
|
||||
// Usage: node migration/compare.mjs (after extract + build + extract --built)
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const INV = join(dirname(fileURLToPath(import.meta.url)), 'inventory')
|
||||
const LANGS = ['en', 'fr', 'nl']
|
||||
|
||||
const LABELS = {
|
||||
en: new Set(["What it's for", 'Why it matters', 'For whom & when', 'Install & use']),
|
||||
fr: new Set(['À quoi ça sert', 'Pourquoi', 'Pour qui & quand', 'Installer & utiliser']),
|
||||
nl: new Set([
|
||||
'Waarvoor dient het',
|
||||
'Waarom het ertoe doet',
|
||||
'Voor wie & wanneer',
|
||||
'Installeren & gebruiken',
|
||||
]),
|
||||
}
|
||||
|
||||
// The six cell lines per language of the two deferred #menace table rows.
|
||||
const EXCLUDED = {
|
||||
en: [
|
||||
'Duration',
|
||||
'Temporary, extended to 3 April 2028',
|
||||
'Permanent',
|
||||
'Status (Jul 2026)',
|
||||
'Adopted 9 July 2026',
|
||||
'Under negotiation, 5th trilogue failed 29 June',
|
||||
],
|
||||
fr: [
|
||||
'Durée',
|
||||
"Temporaire, prolongé jusqu'au 3 avril 2028",
|
||||
'Permanent',
|
||||
'Statut (juil. 2026)',
|
||||
'Adopté le 9 juillet 2026',
|
||||
'En négociation, 5e trilogue échoué le 29 juin',
|
||||
],
|
||||
nl: [
|
||||
'Looptijd',
|
||||
'Tijdelijk, verlengd tot 3 april 2028',
|
||||
'Permanent',
|
||||
'Status (juli 2026)',
|
||||
'Aangenomen op 9 juli 2026',
|
||||
'In onderhandeling, 5e trialoog mislukt op 29 juni',
|
||||
],
|
||||
}
|
||||
|
||||
const unquote = (s) => s.replace(/[“”]/g, '"').replace(/[‘’]/g, "'")
|
||||
const START = /^(PART|PARTIE|DEEL|MEMO) 00 · |^(PART|PARTIE|DEEL) 00 · /
|
||||
const END = /(become ungovernable|devenir ingouvernable|onbestuurbaar worden)/i
|
||||
|
||||
function narrativeWindow(lines, file) {
|
||||
const start = lines.findIndex((l) => START.test(l))
|
||||
// the end sentinel is searched after the start (the new hero h1 also
|
||||
// contains "Become Ungovernable")
|
||||
const end = lines.findIndex((l, idx) => idx > start && END.test(l))
|
||||
if (start === -1 || end === -1 || end < start) {
|
||||
throw new Error(`cannot find narrative window in ${file} (start=${start}, end=${end})`)
|
||||
}
|
||||
return {
|
||||
head: lines.slice(0, start),
|
||||
body: lines.slice(start, end + 1),
|
||||
tail: lines.slice(end + 1),
|
||||
}
|
||||
}
|
||||
|
||||
const report = {}
|
||||
let failed = false
|
||||
|
||||
for (const lang of LANGS) {
|
||||
const legacyAll = readFileSync(join(INV, `legacy-${lang}.txt`), 'utf8')
|
||||
.trimEnd()
|
||||
.split('\n')
|
||||
const builtAll = readFileSync(join(INV, `built-${lang}.txt`), 'utf8')
|
||||
.trimEnd()
|
||||
.split('\n')
|
||||
const legacy = narrativeWindow(legacyAll, `legacy-${lang}.txt`)
|
||||
const built = narrativeWindow(builtAll, `built-${lang}.txt`)
|
||||
|
||||
const excludedPending = [...EXCLUDED[lang]]
|
||||
const stats = { labels: 0, excluded: [], quotes: 0, spacing: [], unexplained: [] }
|
||||
|
||||
let i = 0
|
||||
let j = 0
|
||||
const L = legacy.body
|
||||
const B = built.body.map(unquote)
|
||||
const Braw = built.body
|
||||
while (i < L.length || j < B.length) {
|
||||
const a = L[i]
|
||||
const b = B[j]
|
||||
if (a !== undefined && b !== undefined && a === b) {
|
||||
if (Braw[j] !== b) stats.quotes++
|
||||
i += 1
|
||||
j += 1
|
||||
continue
|
||||
}
|
||||
if (a !== undefined && b !== undefined && a.replace(/\s+/g, '') === b.replace(/\s+/g, '')) {
|
||||
if (Braw[j] !== b) stats.quotes++
|
||||
stats.spacing.push(a.length > 90 ? a.slice(0, 90) + '…' : a)
|
||||
i += 1
|
||||
j += 1
|
||||
continue
|
||||
}
|
||||
if (b !== undefined && LABELS[lang].has(b)) {
|
||||
stats.labels++
|
||||
j++
|
||||
continue
|
||||
}
|
||||
if (a !== undefined && excludedPending.includes(a)) {
|
||||
excludedPending.splice(excludedPending.indexOf(a), 1)
|
||||
stats.excluded.push(a)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
stats.unexplained.push({ line: i + 1, legacy: a ?? '(end)', built: b ?? '(end)' })
|
||||
failed = true
|
||||
if (stats.unexplained.length > 10) break
|
||||
i += 1
|
||||
j += 1
|
||||
}
|
||||
|
||||
report[lang] = {
|
||||
legacy: { chromeHead: legacy.head.length, narrative: L.length, chromeTail: legacy.tail.length },
|
||||
built: { chromeHead: built.head.length, narrative: B.length, chromeTail: built.tail.length },
|
||||
matchedAfterCategories: !stats.unexplained.length,
|
||||
toolCardLabelLines: stats.labels,
|
||||
deferredLegislativeLines: stats.excluded,
|
||||
linesWithSmartQuotes: stats.quotes,
|
||||
tagBoundarySpacingLines: stats.spacing.length,
|
||||
tagBoundarySpacingSamples: stats.spacing.slice(0, 8),
|
||||
unexplained: stats.unexplained,
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(join(INV, 'compare-report.json'), JSON.stringify(report, null, 2) + '\n')
|
||||
for (const [lang, r] of Object.entries(report)) {
|
||||
console.log(
|
||||
`${lang}: narrative legacy=${r.legacy.narrative} built=${r.built.narrative} | ` +
|
||||
`labels=+${r.toolCardLabelLines} excluded=-${r.deferredLegislativeLines.length} ` +
|
||||
`smartquotes=${r.linesWithSmartQuotes} spacing=${r.tagBoundarySpacingLines} ` +
|
||||
`unexplained=${r.unexplained.length}`,
|
||||
)
|
||||
for (const u of r.unexplained) console.log(' MISMATCH', u)
|
||||
}
|
||||
process.exit(failed ? 1 : 0)
|
||||
@@ -1,659 +0,0 @@
|
||||
// One-off content migration: legacy index.html (tri-lingual data-l spans)
|
||||
// -> per-locale MDX section files + language-neutral tools.json + QA inventories.
|
||||
//
|
||||
// Usage:
|
||||
// node migration/extract.mjs # extract MDX + tools.json + anchors + legacy inventories
|
||||
// node migration/extract.mjs --built # dump built-page inventories from dist/ (run after pnpm build)
|
||||
//
|
||||
// Fidelity rules implemented here:
|
||||
// - every visible prose character of the chosen language is preserved
|
||||
// (whitespace-collapsed); inline markup becomes markdown (**/*) in
|
||||
// markdown contexts and JSX-safe HTML in JSX contexts;
|
||||
// - MDX-hostile characters ({ } < > * _ [ ] ` ~ \ |) are emitted as
|
||||
// HTML character references so they render byte-identical;
|
||||
// - external links keep target="_blank" and get rel="noopener noreferrer";
|
||||
// - "www." in plain text is emitted as "www." so remark-gfm cannot
|
||||
// invent autolinks that the legacy page did not have.
|
||||
//
|
||||
// Deliberate exclusions (per migration plan, all listed in diff-report.md):
|
||||
// - topbar / hero / status banner / TOC / footer (UI chrome rebuilt separately);
|
||||
// - the legislative status banner and adopted/extended claims
|
||||
// (#menace table rows "Durée/Duration" and "Statut/Status");
|
||||
// - the runtime simpleicons CDN script and theme/lang toggle JS.
|
||||
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import * as cheerio from 'cheerio'
|
||||
import * as simpleIcons from 'simple-icons'
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const LEGACY = join(ROOT, 'index.html')
|
||||
const OUT_SECTIONS = join(ROOT, 'src/content/sections')
|
||||
const OUT_TOOLS = join(ROOT, 'src/data/tools.json')
|
||||
const OUT_INVENTORY = join(ROOT, 'migration/inventory')
|
||||
const LANGS = ['en', 'fr', 'nl']
|
||||
|
||||
const warnings = []
|
||||
const exclusions = []
|
||||
const nlGaps = []
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// text escaping
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Escape a text node so MDX renders it byte-identical, in md or jsx context. */
|
||||
function escText(s) {
|
||||
return (
|
||||
s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\{/g, '{')
|
||||
.replace(/\}/g, '}')
|
||||
.replace(/\*/g, '*')
|
||||
.replace(/_/g, '_')
|
||||
.replace(/`/g, '`')
|
||||
.replace(/\[/g, '[')
|
||||
.replace(/\]/g, ']')
|
||||
.replace(/~/g, '~')
|
||||
.replace(/\\/g, '\')
|
||||
.replace(/\|/g, '|')
|
||||
// keep non-breaking spaces (French punctuation) visible in the source
|
||||
.replace(/\u00A0/g, ' ')
|
||||
// remark-gfm autolink literals: "www.x" in plain text would become a link
|
||||
.replace(/\bwww\./gi, 'www.')
|
||||
)
|
||||
}
|
||||
|
||||
function escAttr(s) {
|
||||
return s.replace(/&/g, '&').replace(/"/g, '"')
|
||||
}
|
||||
|
||||
// collapse runs of whitespace EXCEPT non-breaking spaces (French punctuation)
|
||||
function collapse(s) {
|
||||
return s.replace(/[^\S\u00A0]+/g, ' ')
|
||||
}
|
||||
|
||||
/** Guard the first character of a markdown block against list/quote/heading parsing. */
|
||||
function guardBlockStart(s) {
|
||||
const m = /^(\d+[.)] |[-+>#=] )/.exec(s)
|
||||
if (!m) return s
|
||||
const c = s.charAt(0)
|
||||
return `&#${c.charCodeAt(0)};` + s.slice(1)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// language filtering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reduce a cloned DOM subtree to one language: in every run of consecutive
|
||||
* [data-l] siblings, unwrap the spans of `lang` and drop the others.
|
||||
* If a run carries no span for `lang`, fall back to English and log it.
|
||||
*/
|
||||
function filterLang($, root, lang, context) {
|
||||
const parents = new Set()
|
||||
$(root)
|
||||
.find('[data-l]')
|
||||
.each((_, el) => parents.add(el.parent))
|
||||
if ($(root).is('[data-l]')) throw new Error('unexpected data-l on root')
|
||||
|
||||
for (const parent of parents) {
|
||||
const runs = []
|
||||
let run = []
|
||||
for (const node of parent.children) {
|
||||
if (node.type === 'tag' && $(node).attr('data-l')) {
|
||||
run.push(node)
|
||||
} else if (node.type === 'text' && node.data.trim() === '') {
|
||||
continue // whitespace between alternates does not break a run
|
||||
} else if (run.length) {
|
||||
runs.push(run)
|
||||
run = []
|
||||
}
|
||||
}
|
||||
if (run.length) runs.push(run)
|
||||
|
||||
for (const r of runs) {
|
||||
let chosen = r.filter((n) => $(n).attr('data-l') === lang)
|
||||
if (chosen.length === 0) {
|
||||
chosen = r.filter((n) => $(n).attr('data-l') === 'en')
|
||||
nlGaps.push({ lang, context, fallback: $(chosen).text().trim().slice(0, 80) })
|
||||
}
|
||||
for (const n of r) {
|
||||
if (chosen.includes(n)) $(n).replaceWith($(n).contents())
|
||||
else $(n).remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($(root).find('[data-l]').length) {
|
||||
throw new Error(`unfiltered data-l spans left in ${context}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// inline serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function serializeLink($, a) {
|
||||
const href = $(a).attr('href') ?? ''
|
||||
const inner = inlineChildren($, a, 'jsx')
|
||||
if (href.startsWith('#')) return `<a href="${escAttr(href)}">${inner}</a>`
|
||||
return `<a href="${escAttr(href)}" target="_blank" rel="noopener noreferrer">${inner}</a>`
|
||||
}
|
||||
|
||||
/** Serialize the children of `el` as inline MDX content. mode: 'md' | 'jsx'. */
|
||||
function inlineChildren($, el, mode) {
|
||||
let out = ''
|
||||
for (const node of el.children) {
|
||||
if (node.type === 'text') {
|
||||
out += escText(collapse(node.data))
|
||||
continue
|
||||
}
|
||||
if (node.type === 'comment') continue
|
||||
if (node.type !== 'tag') throw new Error(`unexpected node type ${node.type}`)
|
||||
const tag = node.tagName
|
||||
const inner = () => inlineChildren($, node, mode)
|
||||
if (tag === 'b' || tag === 'strong') {
|
||||
const hasChildEl = node.children.some((n) => n.type === 'tag')
|
||||
if (mode === 'md' && !hasChildEl) {
|
||||
out += wrapMd(inner(), '**')
|
||||
} else {
|
||||
out += `<${tag}>${inner()}</${tag}>`
|
||||
}
|
||||
} else if (tag === 'em' || tag === 'i') {
|
||||
const hasChildEl = node.children.some((n) => n.type === 'tag')
|
||||
if (mode === 'md' && !hasChildEl) {
|
||||
out += wrapMd(inner(), '*')
|
||||
} else {
|
||||
out += `<${tag}>${inner()}</${tag}>`
|
||||
}
|
||||
} else if (tag === 'sup') {
|
||||
out += `<sup>${inner()}</sup>`
|
||||
} else if (tag === 'br') {
|
||||
out += '<br />'
|
||||
} else if (tag === 'a') {
|
||||
out += serializeLink($, node)
|
||||
} else if (tag === 'span') {
|
||||
const cls = $(node).attr('class')
|
||||
out += cls ? `<span class="${escAttr(cls)}">${inner()}</span>` : `<span>${inner()}</span>`
|
||||
} else {
|
||||
throw new Error(`unhandled inline tag <${tag}> in ${$(el).attr('class') ?? el.tagName}`)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** Wrap trimmed content in markdown emphasis markers, keeping outer spaces outside. */
|
||||
function wrapMd(inner, marker) {
|
||||
const lead = /^\s/.test(inner) ? ' ' : ''
|
||||
const trail = /\s$/.test(inner) ? ' ' : ''
|
||||
const core = inner.trim()
|
||||
if (!core) return inner
|
||||
return `${lead}${marker}${core}${marker}${trail}`
|
||||
}
|
||||
|
||||
function inlineMd($, el) {
|
||||
return guardBlockStart(inlineChildren($, el, 'md').trim())
|
||||
}
|
||||
|
||||
function inlineJsx($, el) {
|
||||
return inlineChildren($, el, 'jsx').trim()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSX block serialization (tables, callouts, grids… anything with structure)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Elements whose content is serialized on a single line (phrasing content).
|
||||
const JSX_LEAF = new Set(['p', 'li', 'h3', 'h4', 'td', 'th', 'span', 'blockquote', 'small', 'b'])
|
||||
// Elements serialized as multi-line containers of element children.
|
||||
const JSX_CONTAINER = new Set(['div', 'ul', 'ol', 'table', 'thead', 'tbody', 'tr', 'details'])
|
||||
|
||||
function jsxAttrs($, el, extra = {}) {
|
||||
const keep = {}
|
||||
const cls = $(el).attr('class')
|
||||
if (cls) keep.class = cls
|
||||
for (const name of ['aria-label', 'aria-hidden', 'colspan', 'rowspan', 'start']) {
|
||||
const v = $(el).attr(name)
|
||||
if (v !== undefined) keep[name] = v
|
||||
}
|
||||
const style = $(el).attr('style')
|
||||
if (style) {
|
||||
// presentation only; styling is rebuilt later
|
||||
warnings.push(`dropped inline style "${style}" on <${el.tagName} class="${cls ?? ''}">`)
|
||||
}
|
||||
Object.assign(keep, extra)
|
||||
return Object.entries(keep)
|
||||
.map(([k, v]) => ` ${k}="${escAttr(String(v))}"`)
|
||||
.join('')
|
||||
}
|
||||
|
||||
function jsxBlock($, el, indent = '', extraAttrs = {}) {
|
||||
const tag = el.tagName
|
||||
if (tag === 'a') return indent + serializeLink($, el)
|
||||
const attrs = jsxAttrs($, el, extraAttrs)
|
||||
const elementKids = el.children.filter((n) => n.type === 'tag')
|
||||
const significantText = el.children.some((n) => n.type === 'text' && n.data.trim() !== '')
|
||||
if (JSX_LEAF.has(tag) || significantText || elementKids.length === 0) {
|
||||
return `${indent}<${tag}${attrs}>${inlineJsx($, el)}</${tag}>`
|
||||
}
|
||||
if (!JSX_CONTAINER.has(tag)) throw new Error(`unhandled jsx block <${tag}>`)
|
||||
const inner = elementKids.map((k) => jsxBlock($, k, indent + ' ')).join('\n')
|
||||
return `${indent}<${tag}${attrs}>\n${inner}\n${indent}</${tag}>`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tool cards
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LEVELS = { Beginner: 1, Intermediate: 2, Advanced: 3 }
|
||||
|
||||
/** Extract the language-neutral facts of a tool card (called on the raw, unfiltered card). */
|
||||
function extractToolFacts($, tg, iconMap, iconSlugs) {
|
||||
const id = $(tg).attr('id')
|
||||
const logo = $(tg).find('.tg-logo').first()
|
||||
const nameEl = $(tg).find('h3.tg-name').first()
|
||||
const nameLocalized = nameEl.find('[data-l]').length > 0
|
||||
// EN plain text of the name, used as the stable language-neutral name
|
||||
const clone = nameEl.clone()
|
||||
clone.find('[data-l]').each((_, s) => {
|
||||
if ($(s).attr('data-l') !== 'en') $(s).remove()
|
||||
})
|
||||
const nameEn = collapse(clone.text()).trim()
|
||||
|
||||
// level: from the tag whose EN text is a known level word
|
||||
let level = null
|
||||
$(tg)
|
||||
.find('.tg-tags .tg-tag')
|
||||
.each((_, t) => {
|
||||
const en = collapse($(t).find('[data-l="en"]').text()).trim()
|
||||
if (LEVELS[en]) level = LEVELS[en]
|
||||
})
|
||||
|
||||
// neutral footer links (localized ones stay in the MDX, tracked by href)
|
||||
const links = []
|
||||
const localizedLinkHrefs = []
|
||||
$(tg)
|
||||
.find('.tg-foot a.tg-link')
|
||||
.each((_, a) => {
|
||||
if ($(a).find('[data-l]').length) {
|
||||
localizedLinkHrefs.push($(a).attr('href') ?? '')
|
||||
return
|
||||
}
|
||||
const label = collapse($(a).text()).trim().replace(/\s*→$/, '')
|
||||
links.push({ label, href: $(a).attr('href') })
|
||||
})
|
||||
|
||||
const note = collapse($(tg).find('.tg-foot .tg-note').text()).trim()
|
||||
const profiles = [...note]
|
||||
|
||||
const legacySlug = iconMap[id] ?? null
|
||||
const candidate = legacySlug && (ICON_RENAMES[legacySlug] ?? legacySlug)
|
||||
const iconSlug = candidate && iconSlugs.has(candidate) ? candidate : null
|
||||
if (legacySlug && !iconSlug) {
|
||||
warnings.push(`icon slug "${legacySlug}" (${id}) not found in simple-icons — set to null`)
|
||||
}
|
||||
|
||||
return {
|
||||
nameLocalized,
|
||||
localizedLinkHrefs,
|
||||
facts: {
|
||||
id,
|
||||
name: nameEn,
|
||||
url: links[0]?.href ?? null,
|
||||
links,
|
||||
iconSlug,
|
||||
monogram: collapse(logo.text()).trim(),
|
||||
color: (/--b:\s*([^;"]+)/.exec(logo.attr('style') ?? '') ?? [])[1]?.trim() ?? null,
|
||||
level,
|
||||
profiles,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Serialize one (already language-filtered) tool card as a <ToolCard> MDX block. */
|
||||
function toolCardMdx($, tg) {
|
||||
const id = $(tg).attr('id')
|
||||
const lines = [`<ToolCard tool="${id}">`]
|
||||
|
||||
// name slot only when the legacy name carried localized content
|
||||
if (tg.__nameLocalized) {
|
||||
const nameEl = $(tg).find('h3.tg-name').first()
|
||||
lines.push(` <Fragment slot="name">${inlineJsx($, nameEl[0])}</Fragment>`)
|
||||
}
|
||||
|
||||
const tags = $(tg)
|
||||
.find('.tg-tags .tg-tag')
|
||||
.map((_, t) => `<span class="tool-tag">${inlineJsx($, t)}</span>`)
|
||||
.get()
|
||||
if (tags.length) {
|
||||
lines.push(` <Fragment slot="tags">${tags.join('')}</Fragment>`)
|
||||
}
|
||||
|
||||
for (const [slot, sel] of [
|
||||
['what', '.lbl-what'],
|
||||
['why', '.lbl-why'],
|
||||
['who', '.lbl-who'],
|
||||
]) {
|
||||
const p = $(tg).find(`p.tg-field > b${sel}`).parent()
|
||||
if (p.length !== 1) throw new Error(`tool ${id}: missing ${slot} field`)
|
||||
const cl = p.clone()
|
||||
cl.find('b.lbl').remove()
|
||||
lines.push(` <Fragment slot="${slot}">${inlineJsx($, cl[0])}</Fragment>`)
|
||||
}
|
||||
|
||||
// extra blocks (e.g. the unaudited-warning box inside t-bitchat)
|
||||
$(tg)
|
||||
.children('div.box')
|
||||
.each((_, box) => {
|
||||
lines.push(jsxBlock($, box, ' ', { slot: 'extra' }))
|
||||
})
|
||||
|
||||
const steps = $(tg).find('details.tg-install > ol.tg-steps')
|
||||
if (steps.length) {
|
||||
const items = steps
|
||||
.children('li')
|
||||
.map((_, li) => ` <li>${inlineJsx($, li)}</li>`)
|
||||
.get()
|
||||
lines.push(` <ol slot="install">\n${items.join('\n')}\n </ol>`)
|
||||
}
|
||||
|
||||
// localized footer links (neutral ones come from tools.json)
|
||||
const localizedHrefs = tg.__localizedLinkHrefs ?? []
|
||||
$(tg)
|
||||
.find('.tg-foot a.tg-link')
|
||||
.each((_, a) => {
|
||||
const href = $(a).attr('href') ?? ''
|
||||
if (!localizedHrefs.includes(href)) return
|
||||
const inner = inlineJsx($, a)
|
||||
lines.push(` <a slot="links" class="tool-link" href="${escAttr(href)}">${inner}</a>`)
|
||||
})
|
||||
|
||||
lines.push('</ToolCard>')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// section blocks -> MDX
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function blockToMdx($, el, sectionId) {
|
||||
const tag = el.tagName
|
||||
const cls = $(el).attr('class') ?? ''
|
||||
if (tag === 'div' && cls === 'part-head') {
|
||||
const parts = []
|
||||
for (const child of el.children.filter((n) => n.type === 'tag')) {
|
||||
if (child.tagName === 'div' && $(child).attr('class') === 'num') {
|
||||
parts.push(`<p class="part-num">${inlineJsx($, child)}</p>`)
|
||||
} else if (child.tagName === 'h2') {
|
||||
parts.push(`## ${inlineMd($, child)}`)
|
||||
} else if (child.tagName === 'p') {
|
||||
parts.push(inlineMd($, child))
|
||||
} else {
|
||||
throw new Error(`unhandled part-head child <${child.tagName}> in #${sectionId}`)
|
||||
}
|
||||
}
|
||||
return parts
|
||||
}
|
||||
if (tag === 'h3') return [`### ${inlineMd($, el)}`]
|
||||
if (tag === 'h4') return [`#### ${inlineMd($, el)}`]
|
||||
if (tag === 'p') return [inlineMd($, el)]
|
||||
if (tag === 'ul' || tag === 'ol') {
|
||||
const items = $(el)
|
||||
.children('li')
|
||||
.map((_, li) => inlineChildren($, li, 'md').trim())
|
||||
.get()
|
||||
const lines = items.map((t, i) => (tag === 'ol' ? `${i + 1}. ${t}` : `- ${t}`))
|
||||
return [lines.join('\n')]
|
||||
}
|
||||
if (tag === 'article' && cls.includes('tg')) return [toolCardMdx($, el)]
|
||||
if (
|
||||
(tag === 'div' &&
|
||||
/^(tablewrap|box( (warn|honest|ok|info))?|swap|namewall|profiles|eco|manifesto)$/.test(
|
||||
cls,
|
||||
)) ||
|
||||
(tag === 'blockquote' && cls === 'pull')
|
||||
) {
|
||||
return [jsxBlock($, el)]
|
||||
}
|
||||
throw new Error(`unhandled block <${tag} class="${cls}"> in #${sectionId}`)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// plain-text inventories (shared by legacy and built dumps)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const TEXT_BLOCKS = new Set([
|
||||
'address',
|
||||
'article',
|
||||
'aside',
|
||||
'blockquote',
|
||||
'body',
|
||||
'button',
|
||||
'details',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'dd',
|
||||
'fieldset',
|
||||
'figure',
|
||||
'footer',
|
||||
'form',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'header',
|
||||
'hr',
|
||||
'li',
|
||||
'main',
|
||||
'nav',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'section',
|
||||
'summary',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'tr',
|
||||
'ul',
|
||||
'small',
|
||||
])
|
||||
|
||||
/** One line of normalized text per block-level element. */
|
||||
function textLines($, root) {
|
||||
const lines = []
|
||||
let buf = ''
|
||||
const flush = () => {
|
||||
const t = buf.replace(/[\s\u00A0]+/g, ' ').trim()
|
||||
if (t) lines.push(t)
|
||||
buf = ''
|
||||
}
|
||||
const walk = (node) => {
|
||||
if (node.type === 'text') {
|
||||
buf += node.data
|
||||
return
|
||||
}
|
||||
if (node.type !== 'tag') return
|
||||
const tag = node.tagName
|
||||
// noscript is excluded: parse5 keeps its children as raw text, which would
|
||||
// leak markup into the dump (it is new chrome, absent from the legacy page)
|
||||
if (['script', 'style', 'svg', 'template', 'noscript'].includes(tag)) return
|
||||
if (tag === 'br') {
|
||||
buf += ' '
|
||||
return
|
||||
}
|
||||
// label <b> elements get their own line so prose lines stay comparable
|
||||
const cls = ($(node).attr('class') ?? '').split(/\s+/)
|
||||
const isBlock = TEXT_BLOCKS.has(tag) || cls.includes('lbl') || cls.includes('tool-label')
|
||||
if (isBlock) flush()
|
||||
for (const c of node.children) walk(c)
|
||||
if (isBlock) flush()
|
||||
}
|
||||
walk(root)
|
||||
flush()
|
||||
return lines
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// main passes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function loadLegacy() {
|
||||
return cheerio.load(readFileSync(LEGACY, 'utf8'))
|
||||
}
|
||||
|
||||
function parseIconMap(html) {
|
||||
const script = /var ICONS=\{([\s\S]*?)\};/.exec(html)
|
||||
if (!script) throw new Error('legacy ICONS map not found')
|
||||
const map = {}
|
||||
for (const m of script[1].matchAll(/'([^']+)':'([^']+)'/g)) map[m[1]] = m[2]
|
||||
return map
|
||||
}
|
||||
|
||||
function simpleIconSlugs() {
|
||||
return new Set(
|
||||
Object.values(simpleIcons)
|
||||
.map((i) => i && i.slug)
|
||||
.filter(Boolean),
|
||||
)
|
||||
}
|
||||
|
||||
// Legacy CDN slugs whose brand was renamed in current simple-icons releases.
|
||||
const ICON_RENAMES = { tutanota: 'tuta', aegis: 'aegisauthenticator' }
|
||||
|
||||
function applyExclusions($, log = false) {
|
||||
// Rule: adopted/extended legislative-status claims are rewritten from
|
||||
// verified sources separately -> drop the two claim rows of the #menace table.
|
||||
$('#menace .tablewrap tbody tr').each((_, tr) => {
|
||||
const firstCell = collapse($(tr).find('td').first().find('[data-l="en"]').text()).trim()
|
||||
if (firstCell === 'Duration' || /^Status/.test(firstCell)) {
|
||||
if (log) {
|
||||
exclusions.push({
|
||||
where: '#menace comparison table',
|
||||
why: 'adopted/extended legislative-status claim, deferred for verified rewrite',
|
||||
text: collapse($(tr).text()).trim(),
|
||||
})
|
||||
}
|
||||
$(tr).remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function extract() {
|
||||
const html = readFileSync(LEGACY, 'utf8')
|
||||
const $ = loadLegacy()
|
||||
if ($('[data-l] [data-l]').length) throw new Error('nested data-l spans — unsupported')
|
||||
|
||||
mkdirSync(OUT_INVENTORY, { recursive: true })
|
||||
|
||||
// ---- anchors.json: every id present in the legacy file, in document order
|
||||
const anchors = $('[id]')
|
||||
.map((_, el) => $(el).attr('id'))
|
||||
.get()
|
||||
writeFileSync(join(OUT_INVENTORY, 'anchors.json'), JSON.stringify(anchors, null, 2) + '\n')
|
||||
|
||||
// ---- legacy per-language inventories (full document, before exclusions)
|
||||
for (const lang of LANGS) {
|
||||
const $full = loadLegacy()
|
||||
filterLang($full, $full('body')[0], lang, 'full-document')
|
||||
const lines = textLines($full, $full('body')[0])
|
||||
writeFileSync(join(OUT_INVENTORY, `legacy-${lang}.txt`), lines.join('\n') + '\n')
|
||||
}
|
||||
|
||||
// ---- tools.json (language-neutral facts, from the raw DOM)
|
||||
const iconMap = parseIconMap(html)
|
||||
const slugs = simpleIconSlugs()
|
||||
const extracted = $('article.tg')
|
||||
.map((_, tg) => extractToolFacts($, tg, iconMap, slugs))
|
||||
.get()
|
||||
const toolsOut = extracted.map((e) => e.facts)
|
||||
mkdirSync(dirname(OUT_TOOLS), { recursive: true })
|
||||
writeFileSync(OUT_TOOLS, JSON.stringify(toolsOut, null, 2) + '\n')
|
||||
const localizedNames = new Set(extracted.filter((e) => e.nameLocalized).map((e) => e.facts.id))
|
||||
const localizedLinks = new Map(extracted.map((e) => [e.facts.id, e.localizedLinkHrefs]))
|
||||
|
||||
// ---- sections -> MDX
|
||||
applyExclusions($, true)
|
||||
const sections = $('main > section').get()
|
||||
let lastPart = 0
|
||||
const written = { en: 0, fr: 0, nl: 0 }
|
||||
sections.forEach((section, idx) => {
|
||||
const id = $(section).attr('id')
|
||||
const order = idx + 1
|
||||
const numEn = collapse($(section).find('.part-head .num [data-l="en"]').text())
|
||||
const m = /(?:PART)\s+(\d+)/.exec(numEn)
|
||||
const part = m ? Number(m[1]) : lastPart
|
||||
lastPart = part
|
||||
|
||||
for (const lang of LANGS) {
|
||||
const $lang = loadLegacy()
|
||||
applyExclusions($lang)
|
||||
const sec = $lang(`main > section#${id.replace(/([^\w-])/g, '\\$1')}`)[0]
|
||||
filterLang($lang, sec, lang, `#${id}`)
|
||||
// flag localized tool names / footer links for the MDX slots
|
||||
$lang(sec)
|
||||
.find('article.tg')
|
||||
.each((_, tg) => {
|
||||
const tgId = $lang(tg).attr('id')
|
||||
tg.__nameLocalized = localizedNames.has(tgId)
|
||||
tg.__localizedLinkHrefs = localizedLinks.get(tgId) ?? []
|
||||
})
|
||||
|
||||
const title = collapse($lang(sec).find('.part-head h2').first().text()).trim()
|
||||
const blocks = []
|
||||
for (const child of sec.children.filter((n) => n.type === 'tag')) {
|
||||
blocks.push(...blockToMdx($lang, child, id))
|
||||
}
|
||||
const fm = [
|
||||
'---',
|
||||
`id: ${JSON.stringify(id)}`,
|
||||
`part: ${part}`,
|
||||
`order: ${order}`,
|
||||
`title: ${JSON.stringify(title)}`,
|
||||
'---',
|
||||
].join('\n')
|
||||
const nn = String(order).padStart(2, '0')
|
||||
const dir = join(OUT_SECTIONS, lang)
|
||||
mkdirSync(dir, { recursive: true })
|
||||
writeFileSync(join(dir, `${nn}-${id}.mdx`), fm + '\n\n' + blocks.join('\n\n') + '\n')
|
||||
written[lang]++
|
||||
}
|
||||
})
|
||||
|
||||
// ---- run log for the diff report
|
||||
const log = {
|
||||
sections: written,
|
||||
tools: toolsOut.length,
|
||||
anchors: anchors.length,
|
||||
nlGaps,
|
||||
exclusions,
|
||||
warnings,
|
||||
}
|
||||
writeFileSync(join(OUT_INVENTORY, 'extract-log.json'), JSON.stringify(log, null, 2) + '\n')
|
||||
console.log(`sections written: en=${written.en} fr=${written.fr} nl=${written.nl}`)
|
||||
console.log(`tools: ${toolsOut.length}, anchors: ${anchors.length}`)
|
||||
console.log(`NL gaps: ${nlGaps.length}`)
|
||||
console.log(`exclusions: ${exclusions.length}`)
|
||||
for (const w of warnings) console.log('WARN:', w)
|
||||
}
|
||||
|
||||
function dumpBuilt() {
|
||||
const pages = { en: 'dist/index.html', fr: 'dist/fr/index.html', nl: 'dist/nl/index.html' }
|
||||
for (const [lang, rel] of Object.entries(pages)) {
|
||||
const $ = cheerio.load(readFileSync(join(ROOT, rel), 'utf8'))
|
||||
const lines = textLines($, $('body')[0])
|
||||
writeFileSync(join(OUT_INVENTORY, `built-${lang}.txt`), lines.join('\n') + '\n')
|
||||
console.log(`built-${lang}.txt: ${lines.length} lines (from ${rel})`)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv.includes('--built')) dumpBuilt()
|
||||
else extract()
|
||||
@@ -1,84 +0,0 @@
|
||||
[
|
||||
"lang-fr",
|
||||
"lang-nl",
|
||||
"lang-en",
|
||||
"theme",
|
||||
"top",
|
||||
"menace",
|
||||
"memo",
|
||||
"messagerie",
|
||||
"t-signal",
|
||||
"t-simplex",
|
||||
"t-session",
|
||||
"t-briar",
|
||||
"t-bitchat",
|
||||
"t-molly",
|
||||
"email",
|
||||
"t-protonmail",
|
||||
"t-tuta",
|
||||
"t-mailbox",
|
||||
"navigateur",
|
||||
"t-mullvadbrowser",
|
||||
"t-firefox",
|
||||
"t-brave",
|
||||
"t-ublock",
|
||||
"t-search",
|
||||
"dns",
|
||||
"t-quad9",
|
||||
"t-mullvaddns",
|
||||
"t-cloudflare",
|
||||
"t-pihole",
|
||||
"vpn",
|
||||
"t-mullvad",
|
||||
"t-protonvpn",
|
||||
"t-ivpn",
|
||||
"censure",
|
||||
"proton",
|
||||
"stockage",
|
||||
"t-protondrive",
|
||||
"t-cryptomator",
|
||||
"t-nc-storage",
|
||||
"motsdepasse",
|
||||
"t-bitwarden",
|
||||
"t-keepassxc",
|
||||
"t-protonpass",
|
||||
"deuxfa",
|
||||
"t-yubikey",
|
||||
"t-aegis",
|
||||
"social",
|
||||
"t-mastodon",
|
||||
"t-nostr",
|
||||
"t-element",
|
||||
"t-fediverse",
|
||||
"argent",
|
||||
"t-bitcoin",
|
||||
"t-monero",
|
||||
"ia",
|
||||
"t-localai",
|
||||
"t-cloudai",
|
||||
"boiteaoutils",
|
||||
"t-maps",
|
||||
"t-notes",
|
||||
"t-visio",
|
||||
"t-photos",
|
||||
"t-alias",
|
||||
"t-backups",
|
||||
"t-databrokers",
|
||||
"selfhost",
|
||||
"t-yunohost",
|
||||
"t-nextcloud",
|
||||
"t-synapse",
|
||||
"t-tailscale",
|
||||
"t-website",
|
||||
"tor",
|
||||
"t-tor",
|
||||
"os",
|
||||
"t-grapheneos",
|
||||
"t-linux",
|
||||
"t-tails",
|
||||
"t-qubes",
|
||||
"telephonie",
|
||||
"opsec",
|
||||
"ecosysteme",
|
||||
"action"
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"legacy": {
|
||||
"chromeHead": 36,
|
||||
"narrative": 789,
|
||||
"chromeTail": 8
|
||||
},
|
||||
"built": {
|
||||
"chromeHead": 6,
|
||||
"narrative": 991,
|
||||
"chromeTail": 9
|
||||
},
|
||||
"matchedAfterCategories": true,
|
||||
"toolCardLabelLines": 208,
|
||||
"deferredLegislativeLines": [
|
||||
"Duration",
|
||||
"Temporary, extended to 3 April 2028",
|
||||
"Permanent",
|
||||
"Status (Jul 2026)",
|
||||
"Adopted 9 July 2026",
|
||||
"Under negotiation, 5th trilogue failed 29 June"
|
||||
],
|
||||
"linesWithSmartQuotes": 138,
|
||||
"tagBoundarySpacingLines": 0,
|
||||
"tagBoundarySpacingSamples": [],
|
||||
"unexplained": []
|
||||
},
|
||||
"fr": {
|
||||
"legacy": {
|
||||
"chromeHead": 36,
|
||||
"narrative": 789,
|
||||
"chromeTail": 8
|
||||
},
|
||||
"built": {
|
||||
"chromeHead": 6,
|
||||
"narrative": 991,
|
||||
"chromeTail": 9
|
||||
},
|
||||
"matchedAfterCategories": true,
|
||||
"toolCardLabelLines": 208,
|
||||
"deferredLegislativeLines": [
|
||||
"Durée",
|
||||
"Temporaire, prolongé jusqu'au 3 avril 2028",
|
||||
"Permanent",
|
||||
"Statut (juil. 2026)",
|
||||
"Adopté le 9 juillet 2026",
|
||||
"En négociation, 5e trilogue échoué le 29 juin"
|
||||
],
|
||||
"linesWithSmartQuotes": 225,
|
||||
"tagBoundarySpacingLines": 0,
|
||||
"tagBoundarySpacingSamples": [],
|
||||
"unexplained": []
|
||||
},
|
||||
"nl": {
|
||||
"legacy": {
|
||||
"chromeHead": 36,
|
||||
"narrative": 789,
|
||||
"chromeTail": 8
|
||||
},
|
||||
"built": {
|
||||
"chromeHead": 6,
|
||||
"narrative": 991,
|
||||
"chromeTail": 9
|
||||
},
|
||||
"matchedAfterCategories": true,
|
||||
"toolCardLabelLines": 208,
|
||||
"deferredLegislativeLines": [
|
||||
"Looptijd",
|
||||
"Tijdelijk, verlengd tot 3 april 2028",
|
||||
"Permanent",
|
||||
"Status (juli 2026)",
|
||||
"Aangenomen op 9 juli 2026",
|
||||
"In onderhandeling, 5e trialoog mislukt op 29 juni"
|
||||
],
|
||||
"linesWithSmartQuotes": 70,
|
||||
"tagBoundarySpacingLines": 0,
|
||||
"tagBoundarySpacingSamples": [],
|
||||
"unexplained": []
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
# Content-migration diff report
|
||||
|
||||
Legacy monolith `index.html` (591 tri-lingual span lines / 659 `data-l` spans per
|
||||
language) → per-locale MDX under `src/content/sections/{en,fr,nl}/` + `src/data/tools.json`.
|
||||
|
||||
## How the inventories were produced
|
||||
|
||||
- `legacy-<lang>.txt` — the FULL legacy document reduced to one language
|
||||
(every `data-l` run resolved), one line of whitespace-collapsed text per
|
||||
block-level element. Chrome (topbar, hero, status banner, TOC, footer) is
|
||||
included so nothing is hidden from this audit.
|
||||
- `built-<lang>.txt` — same dump algorithm over `dist/index.html`,
|
||||
`dist/fr/index.html`, `dist/nl/index.html` after `pnpm build`.
|
||||
One deviation: `<noscript>` is skipped because parse5 exposes its children
|
||||
as raw text (it is new chrome, absent from the legacy page anyway).
|
||||
- `<b class="lbl">` / `<b class="tool-label">` elements are flushed to their
|
||||
own line on both sides so tool-card prose lines stay comparable.
|
||||
- Non-breaking spaces are folded into plain spaces on both sides.
|
||||
- `node migration/compare.mjs` then segments each file into
|
||||
head-chrome / narrative window / tail-chrome (narrative = first `PART 00 ·`
|
||||
line through the manifesto paragraph) and walks the two narrative windows
|
||||
**in order, line by line**. Every line must either match exactly or fall in
|
||||
one of the categories below; anything else exits non-zero.
|
||||
|
||||
## Result
|
||||
|
||||
| lang | legacy narrative lines | built narrative lines | unexplained differences |
|
||||
| ---- | ---------------------- | --------------------- | ----------------------- |
|
||||
| en | 789 | 991 | **0** |
|
||||
| fr | 789 | 991 | **0** |
|
||||
| nl | 789 | 991 | **0** |
|
||||
|
||||
`991 = 789 − 6 (deferred legislative rows) + 208 (tool-card labels)` in every
|
||||
language. Full machine output: `compare-report.json`.
|
||||
|
||||
## Explained differences, exhaustively
|
||||
|
||||
### 1. Tool-card labels are now real text (+208 lines per language)
|
||||
|
||||
In the legacy page the field labels ("What it's for / Why it matters /
|
||||
For whom & when", `À quoi ça sert / Pourquoi / Pour qui & quand`,
|
||||
`Waarvoor dient het / …`) and the `Install & use` summary were **CSS
|
||||
`::before`/`::after` generated content**, so they never appeared in the DOM
|
||||
text. `ToolCard.astro` renders them as real, accessible text:
|
||||
54 cards × 3 field labels + 46 install summaries = 208 new lines per language.
|
||||
The label strings are byte-identical to the legacy CSS `content` values.
|
||||
|
||||
### 2. Deferred legislative-status claims (−6 lines per language)
|
||||
|
||||
Per the migration plan, adopted/extended claims are being rewritten from
|
||||
verified sources separately. Dropped from the `#menace` comparison table
|
||||
(rows 4 and 5), verbatim:
|
||||
|
||||
- EN: `Duration` / `Temporary, extended to 3 April 2028` / `Permanent` /
|
||||
`Status (Jul 2026)` / `Adopted 9 July 2026` / `Under negotiation, 5th trilogue failed 29 June`
|
||||
- FR: `Durée` / `Temporaire, prolongé jusqu'au 3 avril 2028` / `Permanent` /
|
||||
`Statut (juil. 2026)` / `Adopté le 9 juillet 2026` / `En négociation, 5e trilogue échoué le 29 juin`
|
||||
- NL: `Looptijd` / `Tijdelijk, verlengd tot 3 april 2028` / `Permanent` /
|
||||
`Status (juli 2026)` / `Aangenomen op 9 juli 2026` / `In onderhandeling, 5e trialoog mislukt op 29 juni`
|
||||
|
||||
The status banner ("Dernière minute : 9 juillet 2026" block) was excluded
|
||||
wholesale with the chrome (see §4); it is replaced by `StatusBanner.astro`
|
||||
fed from verified sources in the parallel workstream.
|
||||
|
||||
Two borderline in-prose date claims were **kept** (judgment call — they are
|
||||
narrative argument, not adopted/extended status): the 19 March 2026 joint
|
||||
appeal, and the "kept scanning after the legal basis lapsed on 4 April 2026"
|
||||
sentence, both in `#menace`.
|
||||
|
||||
### 3. Typographic quotes (smartypants)
|
||||
|
||||
Astro's default markdown pipeline (`markdown.smartypants`, ON by default)
|
||||
typesets `"` as `“ ”` and `'` as `’` in the rendered pages
|
||||
(en: 138 lines affected, fr: 225, nl: 70). No other smartypants rule fires:
|
||||
the legacy prose contains no ASCII `...` or `--` (its 3 `…` are literal and
|
||||
pass through unchanged). This is the **only character-level transformation**
|
||||
in the entire narrative.
|
||||
|
||||
It could not be disabled from inside this migration's ownership
|
||||
(`astro.config.mjs` is off-limits). If byte-identical glyphs are required,
|
||||
the config owner can add `markdown: { smartypants: false }` — one line — and
|
||||
the compare gate can then drop its quote normalization.
|
||||
|
||||
### 4. Chrome not migrated (rebuilt separately)
|
||||
|
||||
Legacy head-chrome (36 lines/language) and tail-chrome (8 lines/language)
|
||||
deliberately not migrated to MDX:
|
||||
|
||||
- topbar: brand `◆ EXIT CHAT CONTROL`, FR/NL/EN buttons, theme toggle;
|
||||
- hero: stamp (`Mass surveillance · EU · 2026`), eyebrow, `Exit Chat Control_`
|
||||
h1, lede paragraph, three profile meta tags;
|
||||
- status banner: `Breaking: 9 July 2026` + the vote paragraph
|
||||
(superseded content, see §2);
|
||||
- TOC (24 lines) — duplicates the section titles that ARE migrated;
|
||||
- footer: Resources / Sources / Act link lists, GitHub link, disclaimer
|
||||
(`Facts last updated: 9 July 2026`).
|
||||
|
||||
The built pages already contain replacement chrome from the parallel
|
||||
rebuild workstream (TopBar/Footer/StatusBanner components, new hero h1 +
|
||||
description, skip-link, noscript note, rewritten source lists citing
|
||||
official roll-call results). That text intentionally differs from the
|
||||
legacy chrome and is **not** part of this migration's fidelity claim; it
|
||||
sits outside the compared narrative window (built head-chrome: 6 lines,
|
||||
tail-chrome: 9 lines).
|
||||
|
||||
- The legacy inline JS (lang/theme toggles, simpleicons CDN loader) was not
|
||||
migrated by design. Icon rendering will be rebuilt offline later; the
|
||||
legacy CDN slugs are preserved in `tools.json.iconSlug` (validated, §6).
|
||||
|
||||
### 5. NL gaps
|
||||
|
||||
None. Every one of the 649 `data-l` runs carries all three languages
|
||||
(en/fr/nl counts are equal in every run), so the EN-fallback path never
|
||||
fired. `extract-log.json → nlGaps: []`.
|
||||
|
||||
### 6. tools.json notes (language-neutral facts)
|
||||
|
||||
- 54 entries; every entry has `url` (first neutral footer link), full `links`
|
||||
array (21 cards have 2–3 links), `monogram`, `color`, `profiles`
|
||||
(the 🟢🟡🔴 footer note), `level` where the legacy card carried a level tag
|
||||
(34 cards; the other 20 never had one).
|
||||
- `iconSlug` was validated against the installed `simple-icons` package.
|
||||
Renamed upstream and mapped: `tutanota → tuta`, `aegis → aegisauthenticator`.
|
||||
Removed upstream and therefore set to null (6): `briar`, `molly`, `ivpn`,
|
||||
`protonpass`, `nostr`, `standardnotes`. 5 cards never had an icon in the
|
||||
legacy map (`t-bitchat`, `t-databrokers`, `t-mailbox`, `t-website`,
|
||||
`t-yunohost`).
|
||||
- 7 cards have localized display names (e.g. `Firefox (durci/hardened/gehard)`,
|
||||
`Sauvegardes chiffrées / Encrypted backups / Versleutelde back-ups`).
|
||||
`tools.json.name` stores the EN plain text; the localized display name is
|
||||
rendered through the ToolCard `name` slot from each locale's MDX.
|
||||
- One presentational attribute dropped: `style="margin:.6rem 0 .4rem"` on the
|
||||
warning box inside `t-bitchat` (styling is rebuilt later; text intact).
|
||||
|
||||
### 7. Anchors
|
||||
|
||||
`anchors.json` lists all 82 `id=` values of the legacy file. 78 exist in
|
||||
every built page: 23 section ids on `<section>` wrappers, 54 tool ids on
|
||||
ToolCard `<article>` roots, `top` on the hero h1. The 4 missing are the
|
||||
legacy control-widget ids `lang-fr`, `lang-nl`, `lang-en`, `theme`
|
||||
(toggle buttons, never used as link targets — chrome rebuilt separately).
|
||||
|
||||
### 8. External domains for the allowlist review
|
||||
|
||||
The migrated content links to 75 unique external hostnames
|
||||
(`external-domains.txt`). The parallel workstream's
|
||||
`tests/unit/dist-links.test.ts` fails until these are reviewed and added to
|
||||
`src/data/domains-allowlist.json` — that file requires review and is outside
|
||||
this migration's ownership, so it was left untouched.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```
|
||||
node migration/extract.mjs # MDX + tools.json + anchors + legacy dumps
|
||||
pnpm build
|
||||
node migration/extract.mjs --built # built-page dumps
|
||||
node migration/compare.mjs # exits non-zero on any unexplained diff
|
||||
```
|
||||
@@ -1,75 +0,0 @@
|
||||
addy.io
|
||||
adguard.com
|
||||
astro.build
|
||||
bitchat.free
|
||||
bitcoin.org
|
||||
bitwarden.com
|
||||
brave.com
|
||||
briarproject.org
|
||||
cryptomator.org
|
||||
cryptpad.org
|
||||
distrochooser.de
|
||||
distrosea.com
|
||||
duck.ai
|
||||
duckduckgo.com
|
||||
edri.org
|
||||
element.io
|
||||
ente.io
|
||||
fightchatcontrol.eu
|
||||
getaegis.app
|
||||
getsession.org
|
||||
ghost.org
|
||||
grapheneos.org
|
||||
headscale.net
|
||||
immich.app
|
||||
incogni.com
|
||||
jan.ai
|
||||
jitsi.org
|
||||
join-lemmy.org
|
||||
joinmastodon.org
|
||||
joinpeertube.org
|
||||
joplinapp.org
|
||||
keepassxc.org
|
||||
linuxmint.com
|
||||
lmstudio.ai
|
||||
lumo.proton.me
|
||||
mailbox.org
|
||||
molly.im
|
||||
mullvad.net
|
||||
nextcloud.com
|
||||
nextdns.io
|
||||
nostr.com
|
||||
ollama.com
|
||||
one.one.one.one
|
||||
organicmaps.app
|
||||
osmand.net
|
||||
pi-hole.net
|
||||
pixelfed.org
|
||||
posteo.de
|
||||
postmarketos.org
|
||||
proton.me
|
||||
protonvpn.com
|
||||
quad9.net
|
||||
restic.net
|
||||
search.brave.com
|
||||
searx.space
|
||||
signal.org
|
||||
simplelogin.io
|
||||
simplex.chat
|
||||
standardnotes.com
|
||||
start9.com
|
||||
tails.net
|
||||
tailscale.com
|
||||
tuta.com
|
||||
ublockorigin.com
|
||||
umbrel.com
|
||||
www.cnil.fr
|
||||
www.getmonero.org
|
||||
www.ivpn.net
|
||||
www.mozilla.org
|
||||
www.nitrokey.com
|
||||
www.patrick-breyer.de
|
||||
www.qubes-os.org
|
||||
www.torproject.org
|
||||
www.yubico.com
|
||||
yunohost.org
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"sections": {
|
||||
"en": 23,
|
||||
"fr": 23,
|
||||
"nl": 23
|
||||
},
|
||||
"tools": 54,
|
||||
"anchors": 82,
|
||||
"nlGaps": [],
|
||||
"exclusions": [
|
||||
{
|
||||
"where": "#menace comparison table",
|
||||
"why": "adopted/extended legislative-status claim, deferred for verified rewrite",
|
||||
"text": "DuréeDurationLooptijdTemporaire, prolongé jusqu'au 3 avril 2028Temporary, extended to 3 April 2028Tijdelijk, verlengd tot 3 april 2028PermanentPermanentPermanent"
|
||||
},
|
||||
{
|
||||
"where": "#menace comparison table",
|
||||
"why": "adopted/extended legislative-status claim, deferred for verified rewrite",
|
||||
"text": "Statut (juil. 2026)Status (Jul 2026)Status (juli 2026)Adopté le 9 juillet 2026Adopted 9 July 2026Aangenomen op 9 juli 2026En négociation, 5e trilogue échoué le 29 juinUnder negotiation, 5th trilogue failed 29 JuneIn onderhandeling, 5e trialoog mislukt op 29 juni"
|
||||
}
|
||||
],
|
||||
"warnings": [
|
||||
"icon slug \"briar\" (t-briar) not found in simple-icons — set to null",
|
||||
"icon slug \"molly\" (t-molly) not found in simple-icons — set to null",
|
||||
"icon slug \"ivpn\" (t-ivpn) not found in simple-icons — set to null",
|
||||
"icon slug \"protonpass\" (t-protonpass) not found in simple-icons — set to null",
|
||||
"icon slug \"nostr\" (t-nostr) not found in simple-icons — set to null",
|
||||
"icon slug \"standardnotes\" (t-notes) not found in simple-icons — set to null",
|
||||
"dropped inline style \"margin:.6rem 0 .4rem\" on <div class=\"box warn\">",
|
||||
"dropped inline style \"margin:.6rem 0 .4rem\" on <div class=\"box warn\">",
|
||||
"dropped inline style \"margin:.6rem 0 .4rem\" on <div class=\"box warn\">"
|
||||
]
|
||||
}
|
||||
@@ -1,833 +0,0 @@
|
||||
◆ EXIT CHAT CONTROL
|
||||
🇫🇷 FR
|
||||
🇳🇱 NL
|
||||
🇬🇧 EN
|
||||
◐
|
||||
Mass surveillance · EU · 2026
|
||||
A digital sovereignty field manual
|
||||
Exit Chat Control_
|
||||
Chat Control wants to scan your private messages. Here is how to take back control of your conversations, your data and your tools, step by step, from beginner to whistleblower.
|
||||
🟢 Citizen 🟡 Pseudonymous account 🔴 Whistleblower
|
||||
Breaking: 9 July 2026
|
||||
The European Parliament let Chat Control 1.0 through. The motion to reject failed: 314 MEPs against the text, 276 in favour, 17 abstentions, but 361 votes were needed to block it. "Voluntary" message scanning is now allowed until 3 April 2028. Meanwhile the 5th trilogue on Chat Control 2.0 (CSAR) collapsed on 29 June; negotiations continue. The threat hasn't passed, it's settling in.
|
||||
Contents
|
||||
00Understand the threat
|
||||
★Delete / Adopt
|
||||
01Encrypted messaging
|
||||
02Encrypted email & PGP
|
||||
03Browser & search
|
||||
◦Encrypted DNS & Cloudflare
|
||||
04VPN: the truth
|
||||
✋Censorship & ID
|
||||
05Leave Google (Proton)
|
||||
06Encrypted storage
|
||||
07Passwords
|
||||
◦2FA & hardware keys
|
||||
08Decentralised social
|
||||
09Financial sovereignty
|
||||
⚠Conversational AI
|
||||
◦Everyday toolbox
|
||||
10Self-hosting
|
||||
11Tor
|
||||
12Free OS & Linux
|
||||
◦Telephony & device
|
||||
13Anonymity & OPSEC
|
||||
14The free ecosystem
|
||||
15Take action
|
||||
PART 00 · THE WHY
|
||||
Understand the threat
|
||||
Before you change tools, understand what you are protecting against. Otherwise you install random apps and feel safe when you are not.
|
||||
Chat Control 1.0 and 2.0, plainly
|
||||
"Chat Control" is the nickname for two EU texts that allow (or would require) scanning your private messages for child sexual abuse material (CSAM). The stated goal is legitimate; the method, blanket scanning of the communications of unsuspected people, amounts to mass surveillance.
|
||||
Chat Control 1.0
|
||||
Chat Control 2.0 (CSAR)
|
||||
Text
|
||||
Regulation (EU) 2021/1232, ePrivacy derogation
|
||||
Proposal COM/2022/209 (Ylva Johansson, May 2022)
|
||||
Scanning
|
||||
Voluntary, platforms may scan
|
||||
Mandatory via "detection orders" and "risk-mitigation measures"
|
||||
Who
|
||||
Mostly unencrypted US services: Gmail, Messenger/Instagram, Skype, Snapchat, iCloud Mail, Xbox
|
||||
All platforms, including end-to-end encrypted messengers
|
||||
Duration
|
||||
Temporary, extended to 3 April 2028
|
||||
Permanent
|
||||
Status (Jul 2026)
|
||||
Adopted 9 July 2026
|
||||
Under negotiation, 5th trilogue failed 29 June
|
||||
Client-side scanning: the legal wiretap
|
||||
The heart of Chat Control 2.0 is a technique called client-side scanning. The idea: instead of breaking encryption in transit, software is installed directly on your phone to inspect every message, photo or link before it is encrypted and sent. Signal put it in one line: it is "like malware on your device".
|
||||
Technical honesty, read twice
|
||||
Against client-side scanning, neither a VPN nor end-to-end encryption is enough if the app or the operating system cooperates. The scan happens on the device, before encryption: the VPN has nothing to protect, and encryption kicks in too late. What actually protects you is choosing free/open-source software that refuses to implement scanning, favourable jurisdictions, self-hosting, and taking back control of your device (an untampered OS). Everything else in this guide follows from this truth.
|
||||
Worst of all: the tool doesn't even work
|
||||
Everyone's privacy is sacrificed for a technology that fails. The European Parliament's own study concludes no system detects this content without a high error rate (because across billions of messages, even a tiny false-positive rate yields millions of false accusations). Irish police figures show it: of automated reports, only about 20% were actual material, and over 11% were outright false positives. Landmark research ("Bugs in Our Pockets," 2021) further shows that once client-side scanning is installed, it is inevitably repurposed (terrorism, copyright, opinions).
|
||||
The pretext: "protecting children"
|
||||
No one is against protecting minors, which is exactly what makes it such an effective lever. Fighting child abuse serves as an emotional Trojan horse: who would dare object? Under that banner, a principle is made acceptable that, on its own, would be flatly rejected, the automated inspection of the private communications of hundreds of millions of unsuspected people. Children are the stated motive; the real target is everyone's encryption and privacy.
|
||||
The tell: the companies pushing hardest for this scanning are the ones whose business model is surveillance. On 19 March 2026, a joint appeal urged EU lawmakers to entrench "voluntary" detection, signed by:
|
||||
GoogleLinkedInSnapchatMicrosoftTikTokMeta
|
||||
"Failure to do so would be irresponsible." The tech giants' argument for keeping message scanning alive.
|
||||
Those same players even announced they would keep scanning messages after the legal basis lapsed on 4 April 2026. Citizens are thus asked to entrust the inspection of their intimate conversations to the very companies known for harvesting and monetising their data. This is the real face of Chat Control: an attack on privacy wrapped in a motive no one can refuse.
|
||||
This is not "just" surveillance
|
||||
They try to reassure you: "it's only Gmail, Instagram, Snapchat, Messenger." That's false, and it's the most alarming part. Three shifts hide behind that downplaying.
|
||||
It's not targeting, it's dragnet. This doesn't watch suspects: it installs systematic collection of entire populations' communications. Surveillance at state scale, continuously.
|
||||
It's no longer reading, it's the power to block. Software that inspects a message before it's sent can also refuse to send it. Client-side scanning builds the infrastructure of prior censorship: blocking speech before it even reaches its recipient.
|
||||
Once the infrastructure exists, it gets repurposed. A system built "for the children" becomes a general-purpose control tool. The motive changes; the machine stays.
|
||||
The logical next step: the digital euro
|
||||
The same logic is about to reach your money. The ECB's digital euro plan includes a holding cap of about €3,000 per person, bars companies from holding any, and rests on a traceable currency. The ECB swears it won't be "programmable," but the cap and the traceability are very much on the menu, and the framework cleared committee in the European Parliament in June 2026. When it lands, will you say "relax, it's only up to €3,000"? That's the very same rhetorical trap as "it's only Gmail." The real question is never what is controlled today, but the control infrastructure being installed for tomorrow.
|
||||
Which profile are you?
|
||||
No tool is magic and nobody needs to do everything. Find your profile: each section tells you how far to go.
|
||||
Beginner
|
||||
🟢 The ordinary citizen
|
||||
You just want your conversations and private life to stop being scanned and monetised. Goal: everyday privacy, without becoming an expert.
|
||||
Intermediate
|
||||
🟡 The pseudonymous account
|
||||
Information account, activist page, creator: you fear being deanonymised, doxxed, or losing your account. Goal: separate your real identity from your public one.
|
||||
Advanced
|
||||
🔴 The whistleblower
|
||||
Journalist, activist, source, facing a powerful adversary (state, employer). Goal: strong anonymity, anti-correlation, passing documents without being caught.
|
||||
MEMO · THE DIGITAL SWAP
|
||||
Delete / Adopt
|
||||
The big picture at a glance. Each tool is detailed in the numbered sections below.
|
||||
Delete
|
||||
WhatsApp (Meta)
|
||||
Messenger (Meta)
|
||||
Instagram (Meta)
|
||||
Snapchat
|
||||
Skype (Microsoft)
|
||||
Discord
|
||||
Gmail (Google)
|
||||
Outlook (Microsoft)
|
||||
Google Drive / Photos
|
||||
OneDrive (Microsoft)
|
||||
TikTok
|
||||
Telegram (normal chats)
|
||||
Adopt
|
||||
Signal
|
||||
SimpleX Chat
|
||||
Session
|
||||
Bitchat
|
||||
Element (Matrix)
|
||||
Nostr
|
||||
Mastodon
|
||||
Proton Mail
|
||||
Tuta Mail
|
||||
Mullvad (VPN & browser)
|
||||
GrapheneOS
|
||||
PART 01 · FOUNDATIONS · 🟢
|
||||
Encrypted messaging
|
||||
The first move, the one that protects you fastest: leave WhatsApp, Messenger and Telegram for a genuinely encrypted (ideally open-source) messenger.
|
||||
You leave
|
||||
You adopt
|
||||
Why
|
||||
WhatsApp
|
||||
Signal
|
||||
Same simplicity, without Meta or metadata harvesting
|
||||
Messenger
|
||||
Signal / SimpleX
|
||||
Messenger isn't E2EE by default and was scanned under Chat Control 1.0
|
||||
Telegram
|
||||
Signal / SimpleX
|
||||
Telegram isn't end-to-end encrypted by default
|
||||
S
|
||||
Signal
|
||||
🟢 BeginnerReplaces WhatsApp🇺🇸 · open-source
|
||||
An encrypted messenger for your texts, calls, video and groups. The direct replacement for WhatsApp, just as simple.
|
||||
End-to-end encryption by default, non-profit, an audited protocol that is the world standard. Almost no metadata kept, and the team has pledged to leave the EU rather than install scanning.
|
||||
Everyone, starting today. It's the first and most useful step. One catch: a phone number, which you can hide behind a username.
|
||||
Install from signal.org (App Store, Google Play, or the direct APK on Android).
|
||||
Confirm your number by SMS, then set a PIN.
|
||||
Settings > Privacy: create a username to hide your number, and turn on registration lock.
|
||||
Invite your contacts. On desktop, link the desktop app to your phone.
|
||||
signal.org →🟢🟡🔴
|
||||
SX
|
||||
SimpleX Chat
|
||||
🟡 IntermediateNo identifier🌍 · open-source
|
||||
An encrypted messenger with no account, no number and no identifier. You connect by sharing a link or QR code.
|
||||
By design, even the servers can't know who talks to whom. It's currently the strongest option against metadata correlation.
|
||||
Pseudonymous accounts, sensitive contacts, high-risk. A bit younger than Signal, with a few rough edges.
|
||||
Install from simplex.chat (F-Droid, Google Play, App Store or APK).
|
||||
Pick a local display name (never your real name: it stays on your device).
|
||||
To add a contact, share a one-time invitation link or a QR code.
|
||||
Turn on the passcode lock, and (advanced) configure your own SMP servers.
|
||||
simplex.chat →🟡🔴
|
||||
Se
|
||||
Session
|
||||
🟡 IntermediateNo phone🌍 · open-source
|
||||
Signal-grade encryption, but with no phone number and onion routing.
|
||||
Your identity is a random "Session ID." Traffic goes through a decentralised network (Lokinet) that sharply reduces metadata.
|
||||
When a phone number is a liability. Smaller ecosystem, best kept for conversations that matter.
|
||||
Install from getsession.org (all platforms).
|
||||
"Create account" generates your Session ID and a recovery phrase.
|
||||
Write the recovery phrase on paper: it's the only key to your account.
|
||||
Share your Session ID (or QR) with your contacts.
|
||||
getsession.org →🟡🔴
|
||||
Br
|
||||
Briar
|
||||
🔴 AdvancedOffline / P2PAndroid · open-source
|
||||
A peer-to-peer messenger that works with no internet: over Bluetooth, Wi-Fi Direct or Tor.
|
||||
No server, so nothing to shut down or seize. Survives internet shutdowns and network censorship.
|
||||
Protests, disasters, remote areas, blackouts. Android only, and both people need the app.
|
||||
Install from briarproject.org or F-Droid (no iOS).
|
||||
Create a local account (nickname + password), stored only on the phone.
|
||||
Add a nearby contact by scanning their QR code, or remotely via a link.
|
||||
When the network is down, turn on Bluetooth: messages hop device to device.
|
||||
briarproject.org →🔴
|
||||
bit
|
||||
Bitchat
|
||||
🔴 AdvancedBluetooth mesh🌍 · open-source
|
||||
Jack Dorsey's mesh messenger: nearby phones relay messages hop by hop, with no internet, server or account.
|
||||
End-to-end encryption (AES-256-GCM) and a "panic mode" that wipes everything with three taps on the logo.
|
||||
Protests and blackouts, alongside Briar. Use with caution.
|
||||
Caution: unaudited
|
||||
Its own repo warns it "has not received external security review." Great for resilience, but don't stake lives on it yet.
|
||||
Install from the App Store (iOS) or the APK/GitHub (Android).
|
||||
Open the app and pick a nickname. No account to create.
|
||||
Nearby devices auto-discover over Bluetooth.
|
||||
In danger, triple-tap the logo to wipe everything (panic mode).
|
||||
bitchat →🔴
|
||||
M
|
||||
Molly
|
||||
🔴 AdvancedHardened SignalAndroid · open-source
|
||||
A hardened build of Signal for Android, compatible with the same network.
|
||||
Database encrypted at rest, automatic locking, and a "FOSS" variant fully free of Google services.
|
||||
Advanced users, especially on GrapheneOS, with a high threat model. Android only.
|
||||
Add the Molly repo in F-Droid, or grab the APK from molly.im.
|
||||
Choose Molly-FOSS if you want zero Google dependencies.
|
||||
Register with your number (same network as Signal).
|
||||
Set a database password and automatic locking.
|
||||
molly.im →🔴
|
||||
Against Chat Control
|
||||
Prefer open-source apps outside the big US platforms, whose teams have publicly pledged to leave the EU rather than install client-side scanning (Signal has). Reminder: if the OS itself enforces scanning, the app can't help, hence section 12.
|
||||
PART 02 · 🟢
|
||||
Encrypted email & PGP
|
||||
Gmail reads your email and lives in the US, under "Five Eyes" jurisdiction. Leaving Gmail/Outlook is a huge win for minimal effort.
|
||||
Pm
|
||||
Proton Mail
|
||||
🟢 BeginnerReplaces Gmail🇨🇭 · open-source · OpenPGP
|
||||
An encrypted inbox that replaces Gmail or Outlook, and the pivot account for the whole Proton ecosystem.
|
||||
Based in Switzerland, outside "Five Eyes" jurisdiction. OpenPGP-compatible: automatic encryption between Proton accounts and to other PGP services.
|
||||
Everyone. It's the best starting point to leave Google (see section 05 for the rest of the suite).
|
||||
Create an account at proton.me (the free tier is enough to start).
|
||||
Import your old messages and contacts with Easy Switch (built in).
|
||||
Set up forwarding from Gmail and tell your contacts your new address.
|
||||
Install the mobile app, turn on two-factor auth, and use Proton Bridge if you want Thunderbird.
|
||||
proton.me/mail →🟢🟡
|
||||
Tu
|
||||
Tuta
|
||||
🟢 BeginnerReplaces Gmail🇩🇪 · open-source · post-quantum
|
||||
A German email service, fully open-source and free of any Google service.
|
||||
It also encrypts the subject line and some metadata, with post-quantum-resistant encryption. EU jurisdiction.
|
||||
Those who want maximum encrypted metadata and fully free software. It doesn't use PGP: to write encrypted to outsiders, you password-protect the message.
|
||||
Create an account at tuta.com and install the app (available on F-Droid).
|
||||
To write encrypted to a non-user: click the lock and set a password, shared with your correspondent through another channel.
|
||||
Turn on two-factor authentication.
|
||||
tuta.com →🟢🟡
|
||||
mx
|
||||
mailbox.org · Posteo
|
||||
🟢 BeginnerEthical classic mail🇩🇪 · UE · PGP
|
||||
Two ethical, low-cost European email providers, with server-side PGP support.
|
||||
Privacy- and eco-conscious, built on open standards (IMAP, PGP). They work with any mail client.
|
||||
Those who want a "classic" mailbox (read in Thunderbird) but ethical, rather than a closed encrypted app.
|
||||
Create an account (paid, roughly €1 to €3 per month).
|
||||
Enable server-side PGP encryption (the "Guard" feature at mailbox.org).
|
||||
Set the account up in Thunderbird or the mobile app of your choice.
|
||||
mailbox.org →posteo.de →🟢
|
||||
PGP in one minute
|
||||
PGP (Pretty Good Privacy) is key-based encryption: you have a public key you hand out, and a private key you keep secret. Anyone with your public key can send you a message that only you can decrypt. It's the basis of provider-independent encrypted email. Proton Mail handles it automatically; for manual use, OpenPGP works via Thunderbird (built in since v78).
|
||||
PART 03 · 🟢
|
||||
Browser & search
|
||||
Chrome is an advertising tracker. Google Search profiles every query. Replace both.
|
||||
Mb
|
||||
Mullvad Browser
|
||||
🟡🔴 Interm. / AdvancedAnti-fingerprint🇸🇪 · open-source
|
||||
The Tor Browser's browser, but without the Tor network: use it with your VPN for a hard-to-track desktop.
|
||||
Built with the Tor Project, it's designed so all its users present the same digital fingerprint. The result: instead of being unique and thus trackable, you blend into the crowd. It's the best privacy choice on desktop.
|
||||
Those who want to go beyond simple ad-blocking, especially pseudonymous accounts and exposed profiles.
|
||||
Download it from mullvad.net/browser (Windows, macOS, Linux).
|
||||
Use it as-is, without customising: every added extension or setting makes you identifiable again.
|
||||
Pair it with your VPN to also mask your IP address.
|
||||
mullvad.net/browser →🟡🔴
|
||||
Fx
|
||||
Firefox (hardened)
|
||||
🟢 BeginnerReplaces Chrome🌍 · open-source
|
||||
The reference free browser for privacy-respecting daily use.
|
||||
Unlike Chrome (a Google advertising tracker), Firefox is built by a foundation with no interest in profiling you. With a few tweaks (the arkenfox guide) and the uBlock Origin extension, it becomes very solid.
|
||||
Everyone, as a main daily browser.
|
||||
Install Firefox from mozilla.org/firefox.
|
||||
Add the uBlock Origin extension (ad and tracker blocking).
|
||||
In privacy settings, choose "Strict," disable telemetry, and clear cookies on close. To go further, apply the arkenfox guide.
|
||||
mozilla.org/firefox →🟢
|
||||
Bv
|
||||
Brave
|
||||
🟢 BeginnerBlocks everything by default🌍 · open-source
|
||||
A Chromium-based browser that blocks ads and trackers by default, with a bonus private window connected to Tor.
|
||||
It's the simplest option for someone coming from Chrome: same feel, but without the tracking. It blocks YouTube ads natively, offers encrypted video (Brave Talk, based on Jitsi) and one-click Tor access, handy to reach a blocked site.
|
||||
Beginners who want a painless switch from Chrome. (Ignore the crypto/rewarded-ads features if they don't interest you.)
|
||||
Install Brave from brave.com.
|
||||
Set "Shields" to "aggressive," and turn off Brave Rewards if you don't want ads.
|
||||
For Tor: menu > "New private window with Tor," then wait for "Tor connected".
|
||||
brave.com →🟢
|
||||
uB
|
||||
uBlock Origin
|
||||
🟢 BeginnerExtension🌍 · open-source
|
||||
The browser extension that blocks ads, trackers and spy scripts. The highest-return move, in ten seconds.
|
||||
Online advertising is the main vector of surveillance and profiling. Blocking it cuts most tracking, speeds up pages and reduces malware risk.
|
||||
Everyone, on Firefox as well as Chromium browsers.
|
||||
Open your browser's extension store and search "uBlock Origin" (the original, by Raymond Hill).
|
||||
Install it. It works right away, no setup needed.
|
||||
ublockorigin.com →🟢
|
||||
🔎
|
||||
DuckDuckGo · Brave Search · SearXNG
|
||||
🟢🟡 Search enginesReplaces Google Search
|
||||
Search engines that don't profile every query, unlike Google.
|
||||
DuckDuckGo and Brave Search keep no history tied to your identity. Startpage serves Google results without the tracking. SearXNG goes further: a self-hostable metasearch engine, sovereignty down to your search bar.
|
||||
Everyone. SearXNG targets those who want to host their own engine.
|
||||
In your browser settings, change the default engine to DuckDuckGo, Brave Search or Startpage.
|
||||
For SearXNG: use a public instance (directory at searx.space) or host your own (see section 10).
|
||||
duckduckgo.com →search.brave.com →searx.space →🟢🟡
|
||||
NETWORK · DNS · 🟡
|
||||
Encrypted DNS & Cloudflare
|
||||
DNS is the directory that turns a site name (example.com) into a machine address. The catch: by default your internet provider runs that directory, so it sees every site you visit, even when the page is HTTPS. Encrypting your DNS takes that list back from your ISP. It's a quiet setting but one of the most effective.
|
||||
9
|
||||
Quad9
|
||||
🟢 BeginnerMalware-blocking🇨🇭 non-profit
|
||||
An encrypted, Swiss, non-profit DNS resolver that also blocks known malicious domains.
|
||||
Swiss jurisdiction, no retention of your IP address, protection against phishing and malware. An excellent "set and forget" choice.
|
||||
Everyone, especially those who want simplicity and a good jurisdiction.
|
||||
On Android: Settings > Network > Private DNS > enter dns.quad9.net.
|
||||
On iPhone/Mac: install the DoH profile from quad9.net. On PC: set DNS-over-HTTPS in the browser or the system.
|
||||
quad9.net →🟢🟡
|
||||
DNS
|
||||
Mullvad DNS · NextDNS
|
||||
🟡 IntermediateNetwork filtering
|
||||
Two encrypted resolvers that also block ads and trackers for the whole device.
|
||||
Mullvad DNS (Sweden, no-log) is free and offers blocklists. NextDNS is highly customisable, with a dashboard and per-device profiles, but it's US-based and logs by default (turn it off in settings).
|
||||
Those who want system-wide ad-blocking without installing a per-browser extension.
|
||||
Mullvad DNS: use the address shown at mullvad.net/help/dns as your private DNS (same method as Quad9).
|
||||
NextDNS: create a profile at nextdns.io, disable logging, then apply the config to your devices.
|
||||
mullvad.net/dns →nextdns.io →🟡
|
||||
CF
|
||||
Cloudflare 1.1.1.1 (with nuance)
|
||||
🟢 BeginnerFast but centralised🇺🇸
|
||||
Cloudflare's 1.1.1.1 encrypted DNS and WARP app: fast, free, and a real upgrade over your ISP's resolver.
|
||||
It's useful, with an audited no-logging pledge. But be clear-eyed: Cloudflare already sits in front of a huge share of the web (as a technical middleman, it can see plaintext traffic to those sites). Routing your DNS there too means concentrating even more of your footprint with one US company under US law.
|
||||
Fine for everyday use and malware-blocking (1.1.1.1 for Families). Avoid or diversify for sensitive use: prefer Quad9 or Mullvad DNS instead.
|
||||
1.1.1.1 →🟢
|
||||
π
|
||||
Pi-hole · AdGuard Home
|
||||
🔴 AdvancedWhole-network blocking🌍 · open-source
|
||||
An ad and tracker blocker that protects your whole network at once, installed on a small computer like a Raspberry Pi.
|
||||
Where uBlock Origin protects one browser, Pi-hole filters every device in the home, including those where you can't install an extension: phones, smart TVs, connected objects. Ads and trackers are blocked at the DNS level, before they even load. AdGuard Home is an alternative with a more modern interface.
|
||||
Households that want to clean up all their devices at once, and who have (or want) a Raspberry Pi or small server.
|
||||
On a Raspberry Pi (or a container), run Pi-hole's official installer.
|
||||
In your router's settings, set the Pi-hole's address as the DNS server.
|
||||
From now on, every device on your network is filtered automatically.
|
||||
pi-hole.net →adguard home →🔴
|
||||
PART 04 · 🟢
|
||||
VPN: the truth
|
||||
A VPN is useful, but sold with a lot of lies. Here's what it actually protects.
|
||||
What a VPN does NOT do
|
||||
A VPN does not protect you from Chat Control's client-side scanning: that reads your messages on your device, before encryption and therefore before the VPN. It's also not an invisibility cloak: your OS and apps see more than your VPN provider does.
|
||||
What a VPN does well
|
||||
It hides your activity from your ISP and masks your IP address (and thus location) from the sites you visit. Useful against network surveillance, geolocation and censorship. One piece of the puzzle, not the solution.
|
||||
Mv
|
||||
Mullvad VPN
|
||||
🟢🟡🔴 All levelsNo account · cash/Monero🇸🇪 · open-source
|
||||
A VPN that masks your IP address and encrypts your traffic up to its servers, without ever asking for a single piece of personal information.
|
||||
It's the privacy gold standard. No email, no name: at signup you're given a plain account number. Audited no-log policy, a single honest price (€5 per month, no tiers, no false promises), and payment in cash or Monero. That's exactly what matters the day a state tries to cut off VPN access: an account that can't be tied to your identity, and a payment banks can't block.
|
||||
Everyone, from the citizen bypassing an ID check to the high-risk profile. It's the VPN to recommend first.
|
||||
Go to mullvad.net. If the site is blocked in your country, open it through the Tor Browser or its .onion address. Click "Generate account number": no email or name is asked. Save this number in your password manager and never share it, it's your only access key.
|
||||
Choose the duration and payment method. The price is flat. Three routes: bank card (simplest if not blocked), cash sent by post, or cryptocurrency.
|
||||
Anonymous crypto payment: click "Create a one-time payment address." Mullvad shows an address (Bitcoin or Monero) and an exact amount. Send that amount from your wallet. For maximum anonymity, prefer Monero (private by default); Bitcoin is simpler but traceable. See section 09 to buy crypto. Allow about 30 minutes for confirmation.
|
||||
Download the app from mullvad.net/download (via Tor if the site is filtered) and install it. On mobile, use the App Store, Google Play, or the official APK if the app was pulled from your store.
|
||||
Launch the app, enter your account number, then connect to a server outside your country, ideally outside the EU (Switzerland, US…). Enable the "kill switch" (which cuts the internet if the VPN drops) and Mullvad's DNS.
|
||||
mullvad.net →🟢🟡🔴
|
||||
Pv
|
||||
Proton VPN
|
||||
🟢 BeginnerHonest free tier🇨🇭 · open-source
|
||||
The VPN from the Proton ecosystem, with a genuine free tier (rare and honest).
|
||||
Swiss-based, audited, open-source. Its free tier has no ads, no data resale and no data cap (only the number of countries is limited). It's the ideal on-ramp for a beginner, especially if you already use Proton Mail.
|
||||
Beginners, and anyone already in the Proton ecosystem who wants a single account for everything.
|
||||
Go to protonvpn.com and sign in with your Proton account (or create one).
|
||||
Install the app (Windows, macOS, Linux, Android, iOS).
|
||||
Use "Quick Connect" or pick a country, then enable the kill switch and NetShield (ad and tracker blocking).
|
||||
protonvpn.com →🟢
|
||||
iv
|
||||
IVPN
|
||||
🟡🔴 Interm. / AdvancedNo email · no-log🇬🇮 · open-source
|
||||
A small, especially rigorous and transparent VPN provider, on the same model as Mullvad.
|
||||
Audited no-log policy, no mandatory email, payment in crypto or cash. It even offers anti-tracking options (network-level tracker blocking) and signup with no identifier at all.
|
||||
A solid alternative to Mullvad, for pseudonymous and high-risk profiles who want to diversify.
|
||||
At ivpn.net, generate an account (an ID is created for you, no email).
|
||||
Pay in crypto or cash, as with Mullvad.
|
||||
Install the app, connect outside the EU, enable the kill switch and the WireGuard protocol.
|
||||
ivpn.net →🟡🔴
|
||||
CENSURE · DIGITAL IDENTITY · 🟡
|
||||
Censorship & identity checks
|
||||
The same pretext ("protect minors") is used to force identity checks to access the internet. It's the planned end of online anonymity, and therefore a form of censorship. Here's the state of play, then how to escape it lawfully.
|
||||
Where we stand (2025-2026)
|
||||
France. The SREN law (May 2024) tasks regulator Arcom with age verification; since April 2025, covered sites must implement it. A decree imposing age checks was upheld by the Conseil d'État on 15 July 2025, and a bill banning under-15s from social media passed the Assemblée on 26 January 2026, which means verifying the age, and thus the identity, of everyone.
|
||||
UK. Since 25 July 2025, the Online Safety Act requires "highly effective" age verification (ID, credit card, facial estimation). The immediate result: VPN signups jumped over 1,400% within hours. The House of Lords even debated, in early 2026, an age limit on VPN use itself.
|
||||
EU. The Commission unveiled an age-verification app (April 2025), piloted in five countries, designed to plug into the European digital identity wallet (EUDI Wallet, eIDAS 2) that every state must offer by end of 2026. EFF and EDRi warn: the "zero-knowledge" proof is only optional, and this builds an online-ID infrastructure far beyond child protection.
|
||||
Escaping it lawfully
|
||||
The counter comes down to one idea: take an IP address in a country that doesn't impose these checks, and obtain the tools anonymously, even if they get blocked one day.
|
||||
An offshore VPN. Connect to a server outside the filtering country (ideally outside the EU and the "14 Eyes"), with an audited no-log policy. This restores access to networks demanding an ID.
|
||||
Anonymous payment. Pay for the VPN in crypto or cash (Mullvad, IVPN, Proton accept it). Crucial: if VPNs ever get "banned," payment processors could refuse cards; cash and crypto would be the only options left.
|
||||
Tor to fetch a blocked VPN. If the provider's site becomes unreachable from your country, use the Tor Browser (or a .onion address) to download the client. Once installed, the VPN is far faster for daily use.
|
||||
Switch your store country. An Apple/Google account set to another country brings back apps pulled from your local store.
|
||||
The law is shifting, check
|
||||
Bypassing a geo-restriction is legal in most European countries, but the ground shifts fast: Utah (US) has criminalised VPN circumvention, and the UK Lords are eyeing VPN limits. Check your country's law before relying on any method. This guide is about protecting privacy, not hiding a crime.
|
||||
PART 05 · TAKE BACK CONTROL · 🟡
|
||||
Leave Google
|
||||
Google is a single account that knows your email, calendar, files, movements and searches. Replace the whole suite.
|
||||
Google
|
||||
Proton
|
||||
Others
|
||||
Gmail
|
||||
Proton Mail
|
||||
Tuta, mailbox.org
|
||||
Google Drive
|
||||
Proton Drive
|
||||
Nextcloud, Cryptomator
|
||||
Google Calendar
|
||||
Proton Calendar
|
||||
Tuta Calendar
|
||||
Google Password
|
||||
Proton Pass
|
||||
Bitwarden, KeePassXC
|
||||
Google One VPN
|
||||
Proton VPN
|
||||
Mullvad, IVPN
|
||||
The appeal of Proton (Swiss, non-profit foundation, open-source): one account replaces all of Google, with end-to-end encryption and a jurisdiction outside "Five Eyes". You can migrate gradually, service by service. If you'd rather not recentralise on one provider, every row has an independent alternative.
|
||||
PART 06 · 🟡
|
||||
Encrypted storage
|
||||
Pd
|
||||
Proton Drive
|
||||
🟢 BeginnerReplaces Google Drive / iCloud🇨🇭 · E2EE
|
||||
An end-to-end encrypted cloud for your files and photos, part of Proton.
|
||||
Unlike Google Drive or iCloud, Proton can't read your files: they're encrypted before leaving your device. Swiss jurisdiction, same account as Proton Mail.
|
||||
Everyone, as a simple, direct replacement for Google's or Apple's cloud.
|
||||
Enable Proton Drive in your Proton account and install the app.
|
||||
Turn on automatic photo backup, then disable Google's/Apple's.
|
||||
proton.me/drive →🟢🟡
|
||||
Cy
|
||||
Cryptomator
|
||||
🟡 IntermediateEncrypt before upload🇩🇪 · open-source
|
||||
A vault that encrypts your files before uploading them to any cloud, even Google Drive or Dropbox.
|
||||
The perfect trick if you must keep an existing cloud: you keep the convenient service, but the provider only sees encrypted gibberish. You alone hold the key.
|
||||
Those who can't leave a mainstream cloud yet, but want to protect their files right now.
|
||||
Install Cryptomator (desktop and mobile) from cryptomator.org.
|
||||
Create a "vault" in your cloud's synced folder, choose a strong password.
|
||||
Put your files in the vault: they're encrypted then synced automatically.
|
||||
cryptomator.org →🟡🔴
|
||||
Nc
|
||||
Nextcloud
|
||||
🔴 AdvancedYour own cloud🌍 · open-source
|
||||
Your own cloud (files, calendar, contacts, notes) hosted on your server.
|
||||
The ultimate degree of control: your data never leaves your hardware. Detailed in section 10 (Self-hosting).
|
||||
Self-hosters. See the full card in section 10.
|
||||
→ Section 10nextcloud.com →🔴
|
||||
PART 07 · 🟡
|
||||
Password manager
|
||||
One strong, unique password per service: the foundation of everything that follows. Drop the notebook, drop the reused password.
|
||||
Bw
|
||||
Bitwarden
|
||||
🟢 BeginnerBest to start with🌍 · open-source
|
||||
A password vault that remembers, generates and fills your passwords for you, across all your devices.
|
||||
Free, audited, cross-platform, and end-to-end encrypted. You only need to remember one strong password. It's even self-hostable (via Vaultwarden) so you depend on no one.
|
||||
Everyone. It's the foundation of all security: one strong, unique password per service.
|
||||
Create an account at bitwarden.com and choose a long master password (a phrase). Never forget it: it can't be recovered.
|
||||
Install the browser extension and the mobile app.
|
||||
As you log in, let Bitwarden save and then replace your old passwords with generated ones.
|
||||
bitwarden.com →🟢🟡🔴
|
||||
Kp
|
||||
KeePassXC
|
||||
🔴 Advanced100% local🌍 · open-source
|
||||
A local vault: an encrypted file only you hold, with no cloud and no account.
|
||||
Nothing leaves your device, unless you choose to sync the file yourself (for example via Cryptomator). It's the purist's choice, with no middleman at all.
|
||||
Advanced profiles who want full control and zero online dependency.
|
||||
Install KeePassXC (desktop) and a compatible mobile app (KeePassDX on Android, Strongbox on iOS).
|
||||
Create a database file protected by a master password.
|
||||
To have it on all devices, sync that file through an encrypted cloud.
|
||||
keepassxc.org →🔴
|
||||
Pp
|
||||
Proton Pass
|
||||
🟢 BeginnerWith email aliases🇨🇭 · open-source
|
||||
The password manager of the Proton ecosystem, with built-in disposable email aliases.
|
||||
Ideal if you're already on Proton: one account for mail, cloud and passwords. Its email aliases save you from giving your real address everywhere.
|
||||
Those who want everything in one place with Proton.
|
||||
proton.me/pass →🟢🟡
|
||||
SECURITY · 2FA · 🟡
|
||||
Two-factor auth & hardware keys
|
||||
A password, even a strong one, can leak. Two-factor authentication (2FA) adds a second proof at login: even if your password is stolen, the account stays locked. It's essential on your sensitive accounts (email, password manager, social).
|
||||
Avoid SMS 2FA
|
||||
A code sent by SMS is the weak link: it's vulnerable to "SIM-swap" (an attacker gets your number transferred) and to interception over the phone network (SS7). Studies estimate most SIM-swap attempts succeed. Use SMS only as a last resort, never as your main protection.
|
||||
Yk
|
||||
YubiKey · Nitrokey
|
||||
🟡🔴 Interm. / AdvancedFIDO2 hardware key
|
||||
A small physical key (USB, often with NFC) that proves your identity with a single touch.
|
||||
It's the strongest 2FA: it resists phishing, because the key checks the site's real address before responding. Nitrokey is the fully open-hardware, open-firmware alternative. A fake site can't trick you.
|
||||
Those who want maximum security on key accounts. Buy two (one main, one backup).
|
||||
Get two keys from yubico.com or nitrokey.com.
|
||||
In each account's security settings (email, password manager…), add a "security key" or "passkey." Enrol both keys.
|
||||
Keep the backup key in a safe, separate place.
|
||||
yubico.com →nitrokey.com →🟡🔴
|
||||
Ae
|
||||
Aegis · Ente Auth
|
||||
🟢 BeginnerTOTP codes🌍 · open-source
|
||||
Apps that generate the six-digit codes changing every 30 seconds, replacing Google Authenticator.
|
||||
Aegis (Android) keeps your codes in a local encrypted vault, no cloud. Ente Auth adds end-to-end encrypted sync across devices, and has been audited. Far safer than SMS, and free.
|
||||
Everyone: it's the default 2FA level to adopt, ahead of hardware keys.
|
||||
Install Aegis (F-Droid) or Ente Auth (all platforms).
|
||||
On each account, enable "authenticator app" 2FA and scan the QR code shown.
|
||||
Write down the backup codes on paper and make an encrypted backup of your vault.
|
||||
getaegis.app →ente.io/auth →🟢🟡
|
||||
PART 08 · 🟡
|
||||
Decentralised social
|
||||
Instagram, X and Facebook belong to companies that profile you and can censor or delete you overnight. The "fediverse" offers networks owned by their users.
|
||||
Ma
|
||||
Mastodon
|
||||
🟢 BeginnerReplaces X / Twitter🌍 · open-source
|
||||
The X/Twitter alternative: a social network made of thousands of independent servers that talk to each other (the "fediverse").
|
||||
No single owner, no algorithm deciding for you, no arbitrary ban by one company. The feed is chronological, with no ads or manipulation.
|
||||
Everyone. Ideal for information accounts that want a presence no company can cut off.
|
||||
On joinmastodon.org, choose a server (or "instance") that suits you, then create an account.
|
||||
Install an app (the official one, or Ivory, Tusky…) and follow accounts: you see the whole fediverse, whatever their server.
|
||||
joinmastodon.org →🟢🟡
|
||||
No
|
||||
Nostr
|
||||
🟡🔴Un-censorable🌍 · open protocol
|
||||
Not an app but a protocol: a social network where your identity is a simple key pair, like Bitcoin.
|
||||
No email, no password, no account anyone can delete. Your posts travel across decentralised relays and you truly own your audience: no one can ban you for good. You can even receive Bitcoin tips ("zaps") and switch apps anytime without losing your followers.
|
||||
Information accounts and creators who fear platform censorship and want a presence truly their own.
|
||||
Install a client: Damus (iPhone), Amethyst or Primal (Android), or a web version.
|
||||
The app generates your key pair. Back up your private key (nsec) like a Bitcoin seed phrase: on paper, never shared.
|
||||
Create your profile, follow people, enable zaps to receive sats.
|
||||
nostr.com →🟡🔴
|
||||
El
|
||||
Matrix / Element
|
||||
🟡🔴Replaces Discord / Slack🌍 · federated · E2EE
|
||||
Community chat, in the spirit of Discord or Slack, but federated and end-to-end encryptable. Element is the app, Matrix the network.
|
||||
You can host your own server (Synapse) and control your data. Perfect for a collective, a newsroom or an association that wants its own independent space.
|
||||
Communities and groups. See the metadata caveat below.
|
||||
Install Element (element.io) and create an account on a public server, or on your own (section 10).
|
||||
Join or create rooms, and enable end-to-end encryption for private conversations.
|
||||
element.io →🟡🔴
|
||||
Pi
|
||||
Pixelfed · PeerTube · Lemmy
|
||||
🟡Insta · YouTube · Reddit🌍 · open-source
|
||||
The federated cousins of Instagram (Pixelfed), YouTube (PeerTube) and Reddit (Lemmy).
|
||||
Same logic as Mastodon: no single owner, no ad algorithm, no tracking. You post photos, videos or discussions without feeding a profiling machine.
|
||||
Those who want to leave Instagram, YouTube or Reddit without giving up the format.
|
||||
pixelfed.org →peertube →lemmy →🟡
|
||||
Metadata caveat
|
||||
Matrix encrypts content well, but in federation, participating servers see room membership and message timing. For highly sensitive use, prefer SimpleX/Session or self-host your server.
|
||||
PART 09 · 🟡
|
||||
Financial sovereignty
|
||||
Money is surveilled and censorable too. Activists' accounts have been frozen, donations blocked, and every card payment is tracked. Financial privacy is part of sovereignty.
|
||||
₿
|
||||
Bitcoin (self-custody)
|
||||
🟡🔴Censorship-resistant
|
||||
A digital money you hold yourself, without going through a bank.
|
||||
In self-custody (your keys in a wallet only you control), no one can freeze or block your funds. Its privacy isn't perfect (the ledger is public), but it escapes banking censorship, useful the day a payment is refused.
|
||||
Guarding against account freezes or de-banking, and paying anonymously for a service like a VPN.
|
||||
Get some bitcoin (an exchange, or peer-to-peer).
|
||||
Move them right away to a wallet you control: a hardware wallet (Trezor, ColdCard) or an open-source app. Don't leave them on the exchange.
|
||||
Back up your recovery phrase on paper, never as a photo or online.
|
||||
bitcoin.org →🟡🔴
|
||||
ɱ
|
||||
Monero (XMR)
|
||||
🔴 AdvancedPrivate by default
|
||||
The privacy cryptocurrency: amounts, sender and recipient are hidden by default.
|
||||
It's the exact opposite of Bitcoin here: transactions are private by design. It's the best way to pay for a service without leaving a trail back to you.
|
||||
Those who want maximum financial privacy. Note: Monero is already delisted from several European exchanges, so you often need a decentralised swap.
|
||||
Install a wallet (the official one at getmonero.org, or Cake Wallet on mobile).
|
||||
Get XMR through a decentralised exchange service (for example a "swap" from another crypto).
|
||||
Back up your recovery phrase on paper.
|
||||
getmonero.org →🔴
|
||||
Legality & caution
|
||||
Financial privacy is legal. Don't confuse it with tax evasion: declare what must be declared. Cryptocurrencies are volatile and scams are common; never risk what you can't lose, and learn before you act.
|
||||
BLIND SPOT · THE AI MADNESS · 🟡
|
||||
Conversational AI
|
||||
While we fight to encrypt our messages, hundreds of millions of people pour their most intimate thoughts into ChatGPT, Gemini or Copilot, on US servers. This is the blind spot of the whole story. Sheer madness.
|
||||
What you type into a cloud AI is a written, timestamped, stored confession. Worse than a message: you spill your anxieties, your health, your finances, your work secrets, often more honestly than to a friend. And unlike an encrypted messenger, it's all readable in plaintext on the server.
|
||||
What the facts say (2025-2026)
|
||||
Consumer tiers train on your data by default. In January 2026, a US judge ordered OpenAI to produce 20 million ChatGPT conversations as evidence: the affected users were neither notified nor consulted. Deleting a chat does not guarantee it's gone. And in one leak, 47,000 exposed conversations held emails, phone numbers and re-identifiable intimate details.
|
||||
The counter: local AI
|
||||
Ai
|
||||
Ollama · LM Studio · Jan · GPT4All
|
||||
🟡🔴Replaces ChatGPT🌍 · 100% local
|
||||
Software to run an AI model directly on your own computer.
|
||||
Your questions never leave the device: no server, no account, no telemetry, and it works offline. Quality depends on your hardware, but nothing leaks. Ollama and LM Studio are the easiest; Jan and GPT4All aim for maximum privacy.
|
||||
Anyone who uses AI for even slightly personal topics and doesn't want to hand them to a US server.
|
||||
For a gentle start, install LM Studio or Jan (a graphical interface, like a normal app).
|
||||
Download a model offered in the app (for example Llama or Mistral), matched to your machine's power.
|
||||
Chat away: everything happens locally. Ollama is the command-line version for the more technical.
|
||||
lmstudio.ai →jan.ai →ollama.com →🟡🔴
|
||||
☁
|
||||
Duck.ai · Lumo (Proton)
|
||||
🟢🟡Privacy-minded cloud
|
||||
Cloud AI access, but built for privacy, when a local AI isn't possible.
|
||||
Duck.ai (DuckDuckGo) gives anonymised access to several models. Lumo (Proton) encrypts exchanges and doesn't train on them. Far better than consumer ChatGPT, but it's still a remote server.
|
||||
Those who want cloud convenience without feeding the giants. Keep non-sensitive topics there.
|
||||
duck.ai →lumo →🟢🟡
|
||||
The simple rule
|
||||
Never paste into a cloud AI what you wouldn't tell a stranger who's recording: identity, health, passwords, work secrets, confessions. For anything sensitive, use a local AI.
|
||||
REPLACE THE REST · 🟡
|
||||
The everyday toolbox
|
||||
Google and Apple are not just email: maps, notes, calendar, photos, video calls, everything is tied to your identity. Here's how to replace each brick, one at a time, without losing convenience.
|
||||
Om
|
||||
Organic Maps · OsmAnd
|
||||
🟢 BeginnerReplaces Google Maps🌍 · open-source
|
||||
Map and navigation apps, built on OpenStreetMap, that work offline.
|
||||
Google Maps logs every trip and builds a chillingly precise location history. Organic Maps tracks nothing and shows no ads; OsmAnd targets power users (hiking, contour lines, detailed navigation).
|
||||
Everyone. Live traffic and transit are less complete than Google's, but everyday navigation is excellent.
|
||||
Install from F-Droid, the App Store or Google Play.
|
||||
Download the maps of the regions you need in advance for offline use.
|
||||
organicmaps.app →osmand.net →🟢
|
||||
No
|
||||
Standard Notes · Joplin · CryptPad
|
||||
🟢🟡Replaces Docs / Notion🌍 · open-source
|
||||
End-to-end encrypted notes and documents, to replace Google Docs, Notion or Evernote.
|
||||
Standard Notes (by Proton) encrypts your notes by default. Joplin handles Markdown notebooks with optional encryption and the sync of your choice. CryptPad (French) offers a real-time, fully encrypted Google Docs equivalent.
|
||||
Everyone for notes; CryptPad for multi-person collaboration.
|
||||
Standard Notes / Joplin: create an account, install the app, and enable encryption (on by default in Standard Notes, to switch on in Joplin).
|
||||
CryptPad: use cryptpad.fr or another instance, no account needed to start.
|
||||
standardnotes.com →joplinapp.org →cryptpad.org →🟢🟡
|
||||
Vi
|
||||
Jitsi Meet · Element Call
|
||||
🟢🟡Replaces Zoom / Meet🌍 · open-source
|
||||
Video-conferencing tools with no account or heavy install, to replace Zoom, Google Meet or Teams.
|
||||
Jitsi Meet runs in the browser, no account, and can be self-hosted (it's also the basis of Brave Talk). Element Call is end-to-end encrypted and federated via Matrix. For a simple, fully encrypted call, Signal calls also do the job.
|
||||
Everyone. On a public Jitsi server, multi-party calls are transport-encrypted; end-to-end encryption is optional.
|
||||
Jitsi: go to meet.jit.si, create a room name, share the link. Nothing to install.
|
||||
Element Call: from the Element (Matrix) app, start a call in a room.
|
||||
jitsi.org →element.io/element-call →🟢🟡
|
||||
Ph
|
||||
Ente · Immich
|
||||
🟡🔴Replaces Google Photos🌍 · open-source
|
||||
Two ways to keep your photos without handing them to Google or Apple.
|
||||
Ente is a managed, end-to-end encrypted service, with on-device recognition and search: simple and safe. Immich is a Google Photos clone you host yourself (face and object search included), for those comfortable running a server.
|
||||
Ente for everyone; Immich for self-hosters. Note: Immich is not end-to-end encrypted, its security depends on your server.
|
||||
Ente: install the app, create an account, turn on automatic backup of your camera roll.
|
||||
Immich: deploy it on your server (see section 10) via Docker, then connect the mobile app.
|
||||
ente.io →immich.app →🟡🔴
|
||||
@
|
||||
SimpleLogin · addy.io
|
||||
🟢🟡Email aliases🌍 · open-source
|
||||
Disposable email aliases: a unique address per site, forwarding to your real inbox without ever revealing it.
|
||||
You stop giving out your real address everywhere. If a site is breached or spams you, you cut that one alias, and you instantly know who leaked your data. SimpleLogin is integrated with Proton; addy.io offers unlimited aliases on the free tier.
|
||||
Everyone, and especially pseudonymous accounts that want to compartmentalise their signups.
|
||||
Create an account, install the browser extension.
|
||||
At each signup, generate a new alias instead of typing your real address.
|
||||
simplelogin.io →addy.io →🟢🟡
|
||||
Bk
|
||||
Encrypted backups
|
||||
🟡🔴Cryptomator · restic🌍 · open-source
|
||||
A way to back up your phone and computer, encrypting the data before it leaves for any cloud.
|
||||
iCloud/Google backups are often accessible to the provider, and thus to courts. Cryptomator encrypts your files before sending them to any cloud; restic makes automatic encrypted backups; Proton Drive is end-to-end encrypted. On iPhone, enable "Advanced Data Protection" to encrypt your iCloud backups.
|
||||
Everyone should encrypt their backups. Golden rule: two encrypted copies, on two media, in two places.
|
||||
iPhone: Settings > your name > iCloud > Advanced Data Protection: turn it on.
|
||||
PC: install Cryptomator, create a vault, put your files in it before syncing to the cloud.
|
||||
cryptomator.org →restic.net →🟡🔴
|
||||
RG
|
||||
Erase your data (GDPR)
|
||||
🟡Data brokers
|
||||
Getting your personal information removed from the "data brokers" that collect and resell it.
|
||||
In Europe, the GDPR (Article 17) gives you a right to erasure: you can demand, for free, that a company delete your data, in principle within a month. Services like Incogni automate these requests at scale, for a fee.
|
||||
Those who want to reduce their exposure. Note: data reappears over time, and this doesn't touch public records or social media. Doing it yourself is free but tedious.
|
||||
For a manual request: write to the broker citing GDPR Article 17 and ask for deletion.
|
||||
To automate: subscribe to a removal service and let it send the requests on your behalf.
|
||||
cnil.fr →incogni.com →🟡
|
||||
PART 10 · ADVANCED SELF-DEFENCE · 🔴
|
||||
Self-hosting
|
||||
The ultimate level of sovereignty: host your services yourself. Your data lives on your hardware, under the jurisdiction you choose.
|
||||
Yh
|
||||
YunoHost · Umbrel · Start9
|
||||
🔴 AdvancedTurnkey server🌍 · open-source
|
||||
Systems that turn an old PC or a Raspberry Pi into a personal server, with an app catalogue you install in a few clicks.
|
||||
Self-hosting sounds scary, but these tools do the heavy lifting. YunoHost even runs your own email and single sign-on. Umbrel offers a 300+ app store, popular with privacy and Bitcoin folks. Start9 encrypts backups and gives every service a Tor address by default.
|
||||
Advanced profiles ready to spend an afternoon building their own cloud. It's the most radical structural counter to Chat Control.
|
||||
Get an old computer, a Raspberry Pi, or rent a small server (VPS) in a protective country.
|
||||
Install YunoHost, Umbrel or Start9 following their official guide.
|
||||
From the catalogue, install Nextcloud, a Matrix server, a photo gallery, etc.
|
||||
Reach it privately with Tailscale (card below) rather than exposing the server to the public.
|
||||
yunohost.org →umbrel.com →start9.com →🔴
|
||||
Nc
|
||||
Nextcloud
|
||||
🔴 AdvancedReplaces all of Google🇩🇪 · open-source
|
||||
Your own cloud: files, calendar, contacts, photos, notes and collaborative documents, on your server.
|
||||
It's the full replacement for Google Drive, Calendar and Contacts, but at home. You choose the jurisdiction and no one else controls your files. Note: it's "your server" privacy, not end-to-end encryption by default; you must keep it updated.
|
||||
Self-hosters. The easiest path is to install it via YunoHost or Umbrel.
|
||||
nextcloud.com →🔴
|
||||
Mx
|
||||
Matrix Synapse
|
||||
🔴 AdvancedYour chat server🌍 · open-source
|
||||
Your own Matrix chat server, powering Element and encrypted calls.
|
||||
By hosting the server, you also control the metadata (who talks to whom, when), Matrix's weak point in federation. It's community chat that you alone own.
|
||||
Communities, collectives, newsrooms that want their own independent federated space.
|
||||
element.io/server →🔴
|
||||
Ts
|
||||
Tailscale · Headscale · WireGuard
|
||||
🔴 AdvancedPrivate access to your server🌍 · open-source
|
||||
A private encrypted network linking your devices to your server, without ever exposing it on the public internet.
|
||||
Instead of opening ports (and getting attacked), Tailscale builds a WireGuard tunnel between your authorised devices only: your server has no public attack surface. Headscale is the self-hosted version, so you depend on no third party. WireGuard alone gives full control, at the cost of manual setup.
|
||||
Every self-hoster. It's the safe way to reach your Nextcloud or photos from outside.
|
||||
Install Tailscale on your server and on your phone/PC, connect them to the same account.
|
||||
Your devices instantly see each other on a private network; reach your server at its Tailscale address.
|
||||
For zero dependency, replace the coordination server with self-hosted Headscale.
|
||||
tailscale.com →headscale.net →🔴
|
||||
www
|
||||
Your own website
|
||||
🟡🔴Ban-proof your voice
|
||||
A site of your own, on your own domain name, where your content depends on no platform.
|
||||
The day a network deletes or suspends your account, all your work vanishes. A personal site is your anchor: your posts stay there, archived and up to date, whatever happens. It's exactly what cautious information accounts do. A static site (built with Hugo, Astro or Jekyll) is enough and publishes almost for free; for a dynamic blog, a self-hosted WordPress or Ghost does the job.
|
||||
Information accounts, creators, activists, anyone who publishes and fears platform censorship.
|
||||
Buy a domain name (ideally from a privacy-respecting registrar).
|
||||
Choose a static-site generator (Astro, Hugo) and host the output, or install WordPress/Ghost on your server.
|
||||
Always point to it from your social accounts: your audience finds you even if one account falls.
|
||||
astro.build →ghost.org →🟡🔴
|
||||
The jurisdiction point
|
||||
Self-hosting also means choosing where your data lives. A server at home or in a protective country escapes the scanning duties imposed on big platforms. It's the most radical structural counter to Chat Control.
|
||||
PART 11 · 🔴
|
||||
Tor
|
||||
The network that separates who you are from what you do online.
|
||||
Tor
|
||||
Tor Browser
|
||||
🔴 AdvancedNetwork anonymity🌍 · open-source
|
||||
A network that separates who you are from what you do online, accessible via the Tor Browser.
|
||||
Your traffic bounces through several encrypted relays worldwide: none knows both your identity and your destination. It's the tool of journalists and sources, and the way to reach a blocked site (for example to download a censored VPN).
|
||||
High-risk profiles, and anyone who must decouple their activity from their real IP. Use it occasionally, not as an everyday browser.
|
||||
Download the Tor Browser from torproject.org (Windows, macOS, Linux, Android).
|
||||
Launch it and click "Connect." If Tor is blocked in your country, enable a "bridge."
|
||||
Browse without maximising the window, without installing extensions, and without logging into your real accounts.
|
||||
torproject.org →🔴
|
||||
Limits, not magic
|
||||
Tor protects transport, not your habits: log into your real account and you deanonymise yourself. It's slower, and the exit node sees unencrypted traffic (use HTTPS). For serious anonymity, pair it with Tails (section 12).
|
||||
PART 12 · 🔴
|
||||
Free operating systems
|
||||
The keystone. If client-side scanning can be imposed by the operating system itself, the only real counter is to control that OS. That's true on the phone (GrapheneOS) and on the computer (Linux). Don't underestimate this: as long as you stay on Windows, macOS or a Google Android, you don't truly control your machine.
|
||||
Gr
|
||||
GrapheneOS
|
||||
🔴 AdvancedDe-Googled mobileAndroid · Pixel · open-source
|
||||
A fully de-Googled Android, installed on a Pixel phone: mobile sovereignty.
|
||||
It's the structural defence par excellence against an OS or app store that would scan you. Google services in an optional sandbox, per-app network permissions, isolated profiles, auto-reboot that purges keys from memory, a duress PIN that wipes the phone. So respected for security that it's used by spyware targets.
|
||||
Exposed profiles, but also any citizen willing to buy a Pixel to take back control of their phone.
|
||||
Get a compatible Google Pixel phone (it's the only supported hardware, for security reasons).
|
||||
On a computer, go to grapheneos.org/install and follow the web installer (a few clicks, no terminal needed).
|
||||
For your apps, install the free F-Droid store; add sandboxed Google Play only if needed.
|
||||
Set a long passphrase (not a 4-digit PIN) and learn the "Lockdown" mode.
|
||||
grapheneos.org →🔴
|
||||
🐧
|
||||
Linux (on desktop)
|
||||
🟡🔴Replaces Windows / macOS🌍 · free
|
||||
A free operating system for your computer, replacing Windows or macOS. It comes in many flavours, called "distributions."
|
||||
It's many people's blind spot: they encrypt their messages but keep a Windows that streams telemetry to Microsoft nonstop (whose "Recall" feature tried to screenshot the screen continuously), or a macOS that reports the apps you launch. Linux doesn't spy on you, it's free, it revives old computers, and it finally makes you master of your machine. It's easier than its reputation: modern distributions look like Windows or macOS.
|
||||
Anyone can make the switch. To start, Linux Mint (closest to Windows) or Fedora; Debian for stability. No need to wipe anything: you can test first, then install alongside your current system.
|
||||
Not sure which distribution? Answer the distrochooser.de/fr quiz, which points you to the right one for your needs.
|
||||
Try it without installing anything, right in your browser, at distrosea.com.
|
||||
Once convinced, download the distribution's image and create a bootable USB (with Ventoy or Balena Etcher).
|
||||
Boot from the USB to try the system "live," then run the install. Enable the disk encryption it offers.
|
||||
distrochooser.de/fr →distrosea.com →linuxmint.com →🟡🔴
|
||||
Ta
|
||||
Tails
|
||||
🔴 AdvancedAmnesic USB🌍 · all via Tor
|
||||
A system that boots from a USB stick, routes all traffic through Tor and leaves no trace when shut down.
|
||||
On shutdown, everything is forgotten: it's "amnesic." Ideal for sensitive, one-off work on a computer that isn't yours, leaving nothing behind.
|
||||
Whistleblowers, journalists, sources. The reference tool for high-risk work.
|
||||
Download Tails from tails.net and follow the wizard to create the USB stick.
|
||||
Reboot the computer from that USB. Use it, then shut down: everything vanishes.
|
||||
tails.net →🔴
|
||||
Qb
|
||||
Qubes OS · postmarketOS
|
||||
🔴 ExpertCompartmentalisation🌍 · open-source
|
||||
Two niche systems: Qubes compartmentalises your PC into sealed virtual machines; postmarketOS runs Linux on old phones.
|
||||
Qubes isolates each activity (work, banking, risky browsing) in its own compartment: a compromise doesn't reach the rest. postmarketOS extends the life of phones abandoned by their maker.
|
||||
Expert users with a very high threat model.
|
||||
qubes-os.org →postmarketos.org →🔴
|
||||
Harden your device
|
||||
Even without switching OS, you gain a lot: install apps via F-Droid (free store) or Aurora Store, replace Google services with microG, create separate profiles, cut needless network permissions, disable automatic cloud backup and the advertising ID.
|
||||
HARDWARE · 🔴
|
||||
Telephony & physical device
|
||||
The best app is useless if the device itself betrays you. Here are the hardware threats and the moves that neutralise them.
|
||||
The phone number is a tracker
|
||||
Your number ties together your SIM card, your device (its IMEI identifier), your location and your identity (most countries require ID to buy a SIM). It's the thread that lets everything be cross-referenced.
|
||||
Use number-free messengers (SimpleX, Session) for sensitive contacts, and always prefer Signal over SMS.
|
||||
For a separate identity, a "data-only" SIM or prepaid eSIM limits the link at the point of sale (check your country's rules).
|
||||
IMSI-catchers & the phone network
|
||||
"IMSI-catchers" (or "stingrays") are fake cell towers that force phones to connect so they can log their identifier and sometimes intercept communications, often by downgrading to weakly encrypted 2G. In parallel, an old network flaw (SS7) still allows SMS interception and remote phone location: one more reason to drop SMS.
|
||||
Disable 2G in settings (possible on Android and GrapheneOS) to cut the main interception vector.
|
||||
At a protest or in a sensitive area, switch to airplane mode or slip the phone into a Faraday bag (which blocks all signal). The EFF even publishes a free tool, Rayhunter, to detect IMSI-catchers.
|
||||
Biometrics or passcode?
|
||||
A finger can be forced, a passcode can't
|
||||
Your face or finger can be applied to the sensor while you're restrained (at a border, during an arrest); a password in your head cannot. Before a risky situation, force a return to the passcode: on iPhone, hold the side button and a volume button (the "SOS" screen); on Android/GrapheneOS, use Lockdown in the power menu, which disables biometrics until the PIN is entered. Choose a long passphrase, not a four-digit PIN. GrapheneOS even offers a duress PIN that wipes the phone.
|
||||
De-Microsoft, de-Apple
|
||||
Don't forget the computer: Windows streams telemetry nonstop (and its "Recall" feature tried to screenshot the screen continuously), macOS reports the apps you launch. The real exit is Linux (see section 12). Short of that, disable telemetry, the advertising ID and "Recall," and add an outbound firewall (like Little Snitch on Mac).
|
||||
PART 13 · 🔴
|
||||
Anonymity & OPSEC
|
||||
For the pseudonymous account and the whistleblower. Here you no longer protect just a message: you protect an identity.
|
||||
Pseudonymity vs anonymity
|
||||
The golden rule: never cross your real identity with your public one. Not the same email, number, device, network, posting hours or writing style. A single leak links the two.
|
||||
Identity-less accounts : SimpleX and Session need no number; pair them with disposable email aliases. Beware resold, traceable "virtual numbers".
|
||||
Clean metadata : a posted photo often carries the date, device model and sometimes GPS coordinates (EXIF data). Strip it with Metadata Cleaner, mat2 or ExifTool before posting; watch file names and document metadata too.
|
||||
Network anti-correlation : use Tor/Tails to decouple your activity from your home IP. Never mix your personal and pseudonymous networks. A SIM in your name betrays your location no matter what else you do.
|
||||
Compartmentalisation : a device, or at least a profile, dedicated to the public identity, a separate password manager, and never a cross-login between the two worlds.
|
||||
Passing documents as a source
|
||||
SecureDrop is the anonymous submission system many newsrooms use to receive documents from sources. Many outlets also publish a PGP key and a Signal contact. Never submit from your work hardware or usual network.
|
||||
Honesty, don't overestimate yourself
|
||||
Strong anonymity against a state adversary is hard and fallible. This guide gives bearings, not guarantees. If lives depend on it, train with the authoritative references: EFF Surveillance Self-Defense, Freedom of the Press Foundation, Privacy Guides. Strictly defensive and journalistic context.
|
||||
PART 14 · GOING FURTHER
|
||||
The full free ecosystem
|
||||
Fully de-Googling means replacing each brick with a free equivalent. The common logic (free software + control of your device + self-hosting) is the real counter to client-side scanning.
|
||||
Office
|
||||
LibreOffice
|
||||
OnlyOffice
|
||||
Media
|
||||
VLC
|
||||
Jellyfin
|
||||
FreeTube / NewPipe
|
||||
Audacity · OBS
|
||||
GIMP · Inkscape
|
||||
Calibre · Shotcut
|
||||
Cloud & network
|
||||
Nextcloud
|
||||
Pi-hole
|
||||
KDE Connect
|
||||
Free mobile
|
||||
F-Droid · Aurora
|
||||
microG
|
||||
postmarketOS
|
||||
Security
|
||||
Bitwarden
|
||||
KeePassXC
|
||||
Wireshark
|
||||
Email & desktop
|
||||
Thunderbird
|
||||
FairEmail
|
||||
Dev & advanced
|
||||
Termux · git
|
||||
QEMU / KVM · Wine
|
||||
Flatpak · GNU/Linux
|
||||
PART 15 · TAKE ACTION
|
||||
Migration & digital civil disobedience
|
||||
A step-by-step plan
|
||||
Week 1 🟢 : Install Signal and bring your close ones over. Move to Proton Mail or Tuta. Set up a password manager and uBlock Origin.
|
||||
Week 2 🟡 : Move files to an encrypted cloud, replace Chrome/Google Search, get a VPN, open a Mastodon account.
|
||||
Then 🔴 : Depending on your profile: GrapheneOS, Tor/Tails, self-hosting, OPSEC discipline.
|
||||
Common mistakes
|
||||
Believing one tool is enough; reusing your real number for an anonymous account; installing ten apps without changing habits; forgetting the weak link is often the device itself.
|
||||
Sovereignty is collective
|
||||
An encrypted messenger only works if the other person uses it too. Bring your contacts, family and community along. Switching tools is voting with your usage: every person who leaves surveillance platforms weakens the model.
|
||||
Act politically
|
||||
Technology doesn't replace politics. Follow and support the mobilisation: fightchatcontrol.eu, EDRi, the work of Patrick Breyer. Contact your MEPs, that's still what nearly stopped the text.
|
||||
Manifesto
|
||||
Refusing illegitimate mass surveillance is not hiding: it is a lawful, peaceful, civic act. Privacy is a right, not a confession. To encrypt is to vote. To self-host is to disobey. To reclaim your tools is to become ungovernable.
|
||||
Resources
|
||||
Privacy Guides EFF · Surveillance Self-Defense Freedom of the Press Foundation Fight Chat Control
|
||||
Sources · the law
|
||||
Chat Control · Wikipedia Breyer · vote 9 juillet 2026 Chat Control 1.0 vs 2.0 Proton · client-side scanning Tech giants' joint appeal (19 Mar 2026) Big Tech vows to keep scanning
|
||||
Act
|
||||
European Digital Rights (EDRi) Patrick Breyer · Chat Control Signal · blog
|
||||
Open source on GitHub
|
||||
This guide is an information document on privacy protection and the defence of encryption. It advises no illegal activity. Always check the legislative news: the situation moves fast. Facts last updated: 9 July 2026.
|
||||
@@ -1,833 +0,0 @@
|
||||
◆ EXIT CHAT CONTROL
|
||||
🇫🇷 FR
|
||||
🇳🇱 NL
|
||||
🇬🇧 EN
|
||||
◐
|
||||
Surveillance de masse · UE · 2026
|
||||
Manuel de souveraineté numérique
|
||||
Exit Chat Control_
|
||||
Chat Control veut scanner vos messages privés. Voici comment reprendre le contrôle de vos conversations, de vos données et de vos outils, étape par étape, du débutant au lanceur d'alerte.
|
||||
🟢 Citoyen 🟡 Compte pseudonyme 🔴 Lanceur d'alerte
|
||||
Dernière minute : 9 juillet 2026
|
||||
Le Parlement européen a laissé passer Chat Control 1.0. La motion de rejet a échoué : 314 eurodéputés contre le texte, 276 pour, 17 abstentions, mais il fallait 361 voix pour le bloquer. Le scan « volontaire » des messages est autorisé jusqu'au 3 avril 2028. En parallèle, le 5e trilogue sur Chat Control 2.0 (CSAR) a échoué le 29 juin ; les négociations continuent. La menace n'est pas passée, elle s'installe.
|
||||
Sommaire
|
||||
00Comprendre la menace
|
||||
★À supprimer / À adopter
|
||||
01Messagerie chiffrée
|
||||
02E-mail chiffré & PGP
|
||||
03Navigateur & recherche
|
||||
◦DNS chiffré & Cloudflare
|
||||
04VPN : la vérité
|
||||
✋Censure & identité
|
||||
05Quitter Google (Proton)
|
||||
06Stockage chiffré
|
||||
07Mots de passe
|
||||
◦2FA & clés matérielles
|
||||
08Réseaux décentralisés
|
||||
09Souveraineté financière
|
||||
⚠IA conversationnelle
|
||||
◦Boîte à outils quotidienne
|
||||
10Auto-hébergement
|
||||
11Tor
|
||||
12OS libres & Linux
|
||||
◦Téléphonie & appareil
|
||||
13Anonymat & OPSEC
|
||||
14L'écosystème libre
|
||||
15Passer à l'action
|
||||
PARTIE 00 · LE POURQUOI
|
||||
Comprendre la menace
|
||||
Avant de changer d'outils, il faut comprendre ce contre quoi on se protège. Sinon, on installe des applications au hasard et on se croit protégé alors qu'on ne l'est pas.
|
||||
Chat Control 1.0 et 2.0, en clair
|
||||
« Chat Control » est le surnom donné à deux textes européens qui autorisent (ou imposeraient) le scan de vos messages privés à la recherche de contenus pédocriminels (CSAM). L'objectif affiché est légitime ; le moyen employé, le scan généralisé des communications de personnes non suspectes, revient à une surveillance de masse.
|
||||
Chat Control 1.0
|
||||
Chat Control 2.0 (CSAR)
|
||||
Texte
|
||||
Règlement (UE) 2021/1232, dérogation à la directive ePrivacy
|
||||
Proposition COM/2022/209 (Ylva Johansson, mai 2022)
|
||||
Nature du scan
|
||||
Volontaire, les plateformes peuvent scanner
|
||||
Obligatoire via « ordres de détection » et « mesures d'atténuation des risques »
|
||||
Qui est visé
|
||||
Surtout services US non chiffrés : Gmail, Messenger/Instagram, Skype, Snapchat, iCloud Mail, Xbox
|
||||
Toutes les plateformes, y compris les messageries chiffrées de bout en bout
|
||||
Durée
|
||||
Temporaire, prolongé jusqu'au 3 avril 2028
|
||||
Permanent
|
||||
Statut (juil. 2026)
|
||||
Adopté le 9 juillet 2026
|
||||
En négociation, 5e trilogue échoué le 29 juin
|
||||
Le scan côté client : le mouchard légal
|
||||
Le cœur de Chat Control 2.0 est une technique appelée scan côté client (client-side scanning). L'idée : au lieu de casser le chiffrement pendant le transport, on installe un logiciel directement sur votre téléphone qui inspecte chaque message, photo ou lien avant qu'il ne soit chiffré et envoyé. Signal résume ça d'une phrase : c'est « comme un malware sur votre appareil ».
|
||||
Honnêteté technique, à lire deux fois
|
||||
Contre le scan côté client, ni un VPN ni le chiffrement de bout en bout ne suffisent si l'application ou le système d'exploitation coopère. Le scan a lieu sur l'appareil, avant chiffrement : le VPN ne voit rien à protéger, et le chiffrement intervient trop tard. Ce qui protège réellement, c'est de choisir des logiciels libres qui refusent d'implémenter le scan, des juridictions favorables, l'auto-hébergement, et de reprendre le contrôle de son appareil (un OS non piégé). Tout le reste de ce guide découle de cette vérité.
|
||||
Le pire : l'outil ne marche même pas
|
||||
On sacrifie la vie privée de tous pour une technologie qui échoue. La propre étude du Parlement européen conclut qu'aucun système ne détecte ce contenu sans un taux d'erreur élevé (à cause du très grand nombre de messages, un faible taux de faux positifs produit des millions de fausses accusations). Les chiffres de la police irlandaise l'illustrent : sur les signalements automatiques, environ 20 % seulement correspondaient à du vrai contenu, et plus de 11 % étaient des faux positifs manifestes. Des travaux de référence (« Bugs in Our Pockets », 2021) montrent en plus que le scan côté client, une fois installé, est inévitablement détourné vers d'autres usages (terrorisme, droit d'auteur, opinions).
|
||||
Le prétexte : « protéger les enfants »
|
||||
Personne n'est contre la protection des mineurs, et c'est précisément ce qui en fait un levier si efficace. La lutte contre la pédocriminalité sert de cheval de Troie émotionnel : qui oserait s'y opposer ? Sous cette bannière, on fait accepter un principe qui, présenté seul, serait rejeté en bloc, l'inspection automatisée des communications privées de centaines de millions de personnes non suspectes. Les enfants sont le motif affiché ; la cible réelle, c'est le chiffrement et la vie privée de tous.
|
||||
Le plus révélateur : les entreprises qui poussent le plus fort pour ce scan sont celles dont le modèle économique est la surveillance. Le 19 mars 2026, un appel commun a exhorté les législateurs européens à pérenniser la détection « volontaire », signé par :
|
||||
GoogleLinkedInSnapchatMicrosoftTikTokMeta
|
||||
« Ne pas agir serait irresponsable. » L'argument des géants de la tech pour maintenir le scan des messages.
|
||||
Ces mêmes acteurs ont d'ailleurs annoncé vouloir continuer à scanner les messages même après l'expiration de la base légale, le 4 avril 2026. On demande donc aux citoyens de confier l'inspection de leurs conversations intimes aux entreprises précisément connues pour aspirer et monétiser leurs données. Voilà le vrai visage de Chat Control : une attaque contre la vie privée emballée dans un motif que personne ne peut refuser.
|
||||
Ce n'est pas « juste » de la surveillance
|
||||
On cherche à vous rassurer : « ce n'est que Gmail, Instagram, Snapchat, Messenger ». C'est faux, et c'est même le plus scandaleux. Trois basculements se cachent derrière cette minimisation.
|
||||
Ce n'est pas du ciblage, c'est du ratissage. On ne surveille pas des suspects : on installe une collecte systématique des communications de populations entières. C'est de la surveillance à l'échelle étatique, en continu.
|
||||
Ce n'est plus lire, c'est pouvoir bloquer. Un logiciel qui inspecte un message avant son envoi peut aussi refuser de l'envoyer. Le scan côté client crée l'infrastructure de la censure préalable : bloquer une parole avant même qu'elle existe pour son destinataire.
|
||||
Une fois l'infrastructure posée, elle sert à autre chose. Un dispositif construit « pour les enfants » devient un outil polyvalent de contrôle. Le motif change, la machine reste.
|
||||
La suite logique : l'euro numérique
|
||||
La même logique s'apprête à toucher votre argent. Le projet d'euro numérique de la BCE prévoit un plafond de détention d'environ 3 000 € par personne, interdit aux entreprises d'en détenir, et repose sur une monnaie traçable. La BCE jure qu'elle ne sera pas « programmable », mais le plafond et la traçabilité, eux, sont bien au programme, et le cadre a été validé en commission au Parlement européen en juin 2026. Le jour venu, entendrez-vous « tranquille, c'est seulement jusqu'à 3 000 € » ? C'est exactement le même piège rhétorique que « ce n'est que Gmail ». La vraie question n'est jamais ce qui est contrôlé aujourd'hui, mais l'infrastructure de contrôle que l'on installe pour demain.
|
||||
À quel profil appartenez-vous ?
|
||||
Aucun outil n'est magique et personne n'a besoin de tout faire. Repérez votre profil : chaque section indiquera jusqu'où aller.
|
||||
Débutant
|
||||
🟢 Le citoyen ordinaire
|
||||
Vous voulez simplement que vos conversations et votre vie privée cessent d'être scannées et monétisées. Objectif : la confidentialité au quotidien, sans devenir expert.
|
||||
Intermédiaire
|
||||
🟡 Le compte pseudonyme
|
||||
Compte d'information, page militante, créateur : vous craignez d'être déanonymisé, doxxé, ou de perdre votre compte. Objectif : séparer votre identité réelle de votre identité publique.
|
||||
Avancé
|
||||
🔴 Le lanceur d'alerte
|
||||
Journaliste, activiste, source, face à un adversaire puissant (État, employeur). Objectif : anonymat fort, anti-corrélation, transmettre des documents sans se faire prendre.
|
||||
MÉMO · LE GRAND REMPLACEMENT NUMÉRIQUE
|
||||
À supprimer / À adopter
|
||||
La vue d'ensemble en un coup d'œil. Le détail de chaque outil suit dans les sections numérotées.
|
||||
À supprimer
|
||||
WhatsApp (Meta)
|
||||
Messenger (Meta)
|
||||
Instagram (Meta)
|
||||
Snapchat
|
||||
Skype (Microsoft)
|
||||
Discord
|
||||
Gmail (Google)
|
||||
Outlook (Microsoft)
|
||||
Google Drive / Photos
|
||||
OneDrive (Microsoft)
|
||||
TikTok
|
||||
Telegram (chats normaux)
|
||||
À adopter
|
||||
Signal
|
||||
SimpleX Chat
|
||||
Session
|
||||
Bitchat
|
||||
Element (Matrix)
|
||||
Nostr
|
||||
Mastodon
|
||||
Proton Mail
|
||||
Tuta Mail
|
||||
Mullvad (VPN & navigateur)
|
||||
GrapheneOS
|
||||
PARTIE 01 · LES FONDATIONS · 🟢
|
||||
Messagerie chiffrée
|
||||
Le premier geste, celui qui protège le plus vite : quitter WhatsApp, Messenger et Telegram pour une messagerie réellement chiffrée et, idéalement, libre.
|
||||
Vous quittez
|
||||
Vous adoptez
|
||||
Pourquoi
|
||||
WhatsApp
|
||||
Signal
|
||||
Même simplicité, sans Meta ni collecte de métadonnées
|
||||
Messenger
|
||||
Signal / SimpleX
|
||||
Messenger n'est pas chiffré par défaut et fut scanné sous Chat Control 1.0
|
||||
Telegram
|
||||
Signal / SimpleX
|
||||
Telegram n'est pas chiffré de bout en bout par défaut
|
||||
S
|
||||
Signal
|
||||
🟢 DébutantRemplace WhatsApp🇺🇸 · open-source
|
||||
Une messagerie chiffrée pour vos textos, appels, visios et groupes. C'est le remplaçant direct de WhatsApp, en aussi simple.
|
||||
Chiffrement de bout en bout par défaut, à but non lucratif, protocole audité qui sert de référence mondiale. Presque aucune métadonnée conservée, et l'équipe a promis de quitter l'UE plutôt que d'installer un scan.
|
||||
Tout le monde, dès aujourd'hui. C'est le premier geste et le plus utile. Seule contrainte : un numéro de téléphone, que l'on peut masquer derrière un nom d'utilisateur.
|
||||
Installez depuis signal.org (App Store, Google Play, ou l'APK direct sur Android).
|
||||
Confirmez votre numéro par SMS, puis choisissez un code PIN.
|
||||
Réglages > Confidentialité : créez un nom d'utilisateur pour cacher votre numéro, et activez le verrou d'inscription.
|
||||
Invitez vos proches. Sur ordinateur, liez l'application de bureau à votre téléphone.
|
||||
signal.org →🟢🟡🔴
|
||||
SX
|
||||
SimpleX Chat
|
||||
🟡 IntermédiaireSans identifiant🌍 · open-source
|
||||
Une messagerie chiffrée sans aucun compte, sans numéro et sans identifiant. On se connecte en partageant un lien ou un QR code.
|
||||
Par conception, même les serveurs ignorent qui parle à qui. C'est aujourd'hui l'option la plus protectrice contre la corrélation des métadonnées.
|
||||
Comptes pseudonymes, contacts sensibles, haut risque. Un peu plus jeune que Signal, avec quelques aspérités.
|
||||
Installez depuis simplex.chat (F-Droid, Google Play, App Store ou APK).
|
||||
Choisissez un nom d'affichage local (jamais votre vrai nom : il ne quitte pas votre appareil).
|
||||
Pour ajouter un contact, partagez un lien d'invitation à usage unique ou un QR code.
|
||||
Activez le verrou par code, et (avancé) configurez vos propres serveurs SMP.
|
||||
simplex.chat →🟡🔴
|
||||
Se
|
||||
Session
|
||||
🟡 IntermédiaireSans numéro🌍 · open-source
|
||||
Un chiffrement à la Signal, mais sans numéro de téléphone et avec un routage en oignon.
|
||||
Votre identité est un « Session ID » aléatoire. Le trafic passe par un réseau décentralisé (Lokinet) qui réduit fortement les métadonnées.
|
||||
Quand un numéro de téléphone est un risque. Écosystème plus petit, à réserver aux échanges qui comptent.
|
||||
Installez depuis getsession.org (toutes plateformes).
|
||||
« Create account » génère votre Session ID et une phrase de récupération.
|
||||
Notez la phrase de récupération sur papier : c'est la seule clé de votre compte.
|
||||
Partagez votre Session ID (ou QR) à vos contacts.
|
||||
getsession.org →🟡🔴
|
||||
Br
|
||||
Briar
|
||||
🔴 AvancéHors-ligne / P2PAndroid · open-source
|
||||
Une messagerie pair-à-pair qui fonctionne sans internet : par Bluetooth, Wi-Fi Direct ou Tor.
|
||||
Aucun serveur, donc rien à couper ni à saisir. Résiste aux coupures d'internet et à la censure réseau.
|
||||
Manifestations, catastrophes, zones isolées, blackout. Android uniquement, et les deux personnes doivent avoir l'application.
|
||||
Installez depuis briarproject.org ou F-Droid (pas d'iOS).
|
||||
Créez un compte local (pseudo + mot de passe), stocké seulement sur le téléphone.
|
||||
Ajoutez un contact proche en scannant son QR code, ou à distance via un lien.
|
||||
Quand le réseau est coupé, activez le Bluetooth : les messages passent d'appareil à appareil.
|
||||
briarproject.org →🔴
|
||||
bit
|
||||
Bitchat
|
||||
🔴 AvancéMesh Bluetooth🌍 · open-source
|
||||
La messagerie mesh de Jack Dorsey : les téléphones proches se relaient les messages de proche en proche, sans internet ni serveur ni compte.
|
||||
Chiffrement de bout en bout (AES-256-GCM) et un « mode panique » qui efface tout en trois tapotements sur le logo.
|
||||
Manifestations et blackout, en complément de Briar. À utiliser avec prudence.
|
||||
Prudence : non audité
|
||||
Son propre dépôt avertit qu'il « n'a pas reçu de revue de sécurité externe ». Génial pour la résilience, mais n'en faites pas dépendre des vies pour l'instant.
|
||||
Installez depuis l'App Store (iOS) ou l'APK/GitHub (Android).
|
||||
Ouvrez l'application et choisissez un pseudo. Aucun compte à créer.
|
||||
Les appareils proches se découvrent automatiquement en Bluetooth.
|
||||
En cas de danger, triple-tapez le logo pour tout effacer (mode panique).
|
||||
bitchat →🔴
|
||||
M
|
||||
Molly
|
||||
🔴 AvancéSignal durciAndroid · open-source
|
||||
Une version durcie de Signal pour Android, compatible avec le même réseau.
|
||||
Base de données chiffrée au repos, verrouillage automatique, et une variante « FOSS » entièrement sans services Google.
|
||||
Utilisateurs avancés, notamment sur GrapheneOS, avec un modèle de menace élevé. Android uniquement.
|
||||
Ajoutez le dépôt Molly dans F-Droid, ou récupérez l'APK sur molly.im.
|
||||
Choisissez Molly-FOSS si vous voulez zéro dépendance Google.
|
||||
Enregistrez-vous avec votre numéro (même réseau que Signal).
|
||||
Définissez un mot de passe de base de données et le verrouillage automatique.
|
||||
molly.im →🔴
|
||||
Face à Chat Control
|
||||
Privilégiez des applications libres, hors des grandes plateformes américaines, dont les équipes ont publiquement promis de quitter l'UE plutôt que d'installer un scan côté client (c'est le cas de Signal). Rappel : si l'OS lui-même impose le scan, l'application n'y peut rien, d'où l'importance de la section 12.
|
||||
PARTIE 02 · 🟢
|
||||
E-mail chiffré & PGP
|
||||
Gmail lit vos e-mails et vit aux États-Unis, sous la juridiction des « Five Eyes ». Quitter Gmail/Outlook est un gain énorme pour un effort minime.
|
||||
Pm
|
||||
Proton Mail
|
||||
🟢 DébutantRemplace Gmail🇨🇭 · open-source · OpenPGP
|
||||
Une boîte mail chiffrée qui remplace Gmail ou Outlook, et le compte pivot de tout l'écosystème Proton.
|
||||
Basé en Suisse, hors juridiction des « Five Eyes ». Compatible OpenPGP : chiffrement automatique entre comptes Proton et vers les autres services PGP.
|
||||
Tout le monde. C'est le meilleur point de départ pour quitter Google (voir section 05 pour le reste de la suite).
|
||||
Créez un compte sur proton.me (offre gratuite suffisante pour commencer).
|
||||
Importez vos anciens messages et contacts avec Easy Switch (intégré).
|
||||
Mettez une redirection depuis Gmail et prévenez vos contacts de votre nouvelle adresse.
|
||||
Installez l'application mobile, activez la double authentification, et utilisez Proton Bridge si vous tenez à Thunderbird.
|
||||
proton.me/mail →🟢🟡
|
||||
Tu
|
||||
Tuta
|
||||
🟢 DébutantRemplace Gmail🇩🇪 · open-source · post-quantique
|
||||
Une messagerie e-mail allemande, entièrement open-source et sans aucun service Google.
|
||||
Elle chiffre aussi l'objet et une partie des métadonnées, avec un chiffrement résistant à l'informatique quantique. Juridiction UE.
|
||||
Ceux qui veulent le maximum de métadonnées chiffrées et du 100 % libre. Elle n'utilise pas PGP : pour écrire chiffré à l'extérieur, on protège le message par mot de passe.
|
||||
Créez un compte sur tuta.com et installez l'application (disponible sur F-Droid).
|
||||
Pour écrire à un non-utilisateur de façon chiffrée : cliquez sur le cadenas et définissez un mot de passe, transmis à votre correspondant par un autre canal.
|
||||
Activez la double authentification.
|
||||
tuta.com →🟢🟡
|
||||
mx
|
||||
mailbox.org · Posteo
|
||||
🟢 DébutantMail classique éthique🇩🇪 · UE · PGP
|
||||
Deux fournisseurs de mail européens, éthiques et peu coûteux, avec support PGP côté serveur.
|
||||
Respectueux de la vie privée et de l'écologie, sur des standards ouverts (IMAP, PGP). Ils fonctionnent avec n'importe quel logiciel de messagerie.
|
||||
Ceux qui veulent un mail « classique » (relevé dans Thunderbird) mais éthique, plutôt qu'une messagerie chiffrée fermée.
|
||||
Créez un compte (payant, de l'ordre de 1 à 3 € par mois).
|
||||
Activez le chiffrement PGP côté serveur (fonction « Guard » chez mailbox.org).
|
||||
Configurez le compte dans Thunderbird ou l'application mobile de votre choix.
|
||||
mailbox.org →posteo.de →🟢
|
||||
PGP en une minute
|
||||
PGP (Pretty Good Privacy) est le principe du chiffrement à clés : vous avez une clé publique que vous distribuez, et une clé privée que vous gardez secrète. Quiconque a votre clé publique peut vous écrire un message que vous seul pouvez déchiffrer. C'est la base de l'e-mail chiffré indépendamment de tout fournisseur. Proton Mail le gère automatiquement ; pour un usage manuel, on utilise OpenPGP (via Thunderbird, intégré depuis la version 78).
|
||||
PARTIE 03 · 🟢
|
||||
Navigateur & recherche
|
||||
Chrome est un mouchard publicitaire. Google Search profile chaque requête. On remplace les deux.
|
||||
Mb
|
||||
Mullvad Browser
|
||||
🟡🔴 Interm. / AvancéAnti-empreinte🇸🇪 · open-source
|
||||
Le navigateur du Tor Browser, mais sans le réseau Tor : à utiliser avec votre VPN pour un ordinateur difficile à pister.
|
||||
Développé avec le Tor Project, il est conçu pour que tous ses utilisateurs présentent la même empreinte numérique. Résultat : au lieu d'être unique et donc traçable, vous vous fondez dans la masse. C'est le meilleur choix de vie privée sur ordinateur.
|
||||
Ceux qui veulent aller au-delà du simple blocage de pub, notamment les comptes pseudonymes et les profils exposés.
|
||||
Téléchargez-le sur mullvad.net/browser (Windows, macOS, Linux).
|
||||
Utilisez-le tel quel, sans le personnaliser : chaque extension ou réglage ajouté vous rend à nouveau identifiable.
|
||||
Associez-le à votre VPN pour masquer aussi votre adresse IP.
|
||||
mullvad.net/browser →🟡🔴
|
||||
Fx
|
||||
Firefox (durci)
|
||||
🟢 DébutantRemplace Chrome🌍 · open-source
|
||||
Le navigateur libre de référence pour un usage quotidien respectueux de la vie privée.
|
||||
Contrairement à Chrome (un mouchard publicitaire de Google), Firefox est développé par une fondation et n'a pas d'intérêt à vous profiler. Avec quelques réglages (le guide arkenfox) et l'extension uBlock Origin, il devient très solide.
|
||||
Tout le monde, comme navigateur principal au quotidien.
|
||||
Installez Firefox depuis mozilla.org/firefox.
|
||||
Ajoutez l'extension uBlock Origin (blocage des pubs et traqueurs).
|
||||
Dans les réglages de vie privée, choisissez « Stricte », désactivez la télémétrie, et videz les cookies à la fermeture. Pour aller plus loin, appliquez le guide arkenfox.
|
||||
mozilla.org/firefox →🟢
|
||||
Bv
|
||||
Brave
|
||||
🟢 DébutantBloque tout par défaut🌍 · open-source
|
||||
Un navigateur basé sur Chromium qui bloque publicités et traqueurs par défaut, avec en prime une fenêtre privée connectée à Tor.
|
||||
C'est l'option la plus simple pour quelqu'un qui vient de Chrome : même ergonomie, mais sans le pistage. Il bloque les pubs YouTube nativement, propose une visio chiffrée (Brave Talk, basée sur Jitsi) et un accès Tor en un clic, pratique pour récupérer un site bloqué.
|
||||
Les débutants qui veulent un changement indolore depuis Chrome. (Ignorez les fonctions crypto/pubs récompensées si elles ne vous intéressent pas.)
|
||||
Installez Brave depuis brave.com.
|
||||
Réglez les « Boucliers » (Shields) sur « agressif », et désactivez Brave Rewards si vous ne voulez pas de pubs.
|
||||
Pour Tor : menu > « Nouvelle fenêtre privée avec Tor », puis attendez « Tor connecté ».
|
||||
brave.com →🟢
|
||||
uB
|
||||
uBlock Origin
|
||||
🟢 DébutantExtension🌍 · open-source
|
||||
L'extension de navigateur qui bloque publicités, traqueurs et scripts espions. Le geste le plus rentable, en dix secondes.
|
||||
La publicité en ligne est le principal vecteur de surveillance et de profilage. La bloquer coupe l'essentiel du pistage, accélère les pages et réduit les risques de logiciels malveillants.
|
||||
Tout le monde, sur Firefox comme sur les navigateurs Chromium.
|
||||
Ouvrez le magasin d'extensions de votre navigateur et cherchez « uBlock Origin » (l'original, par Raymond Hill).
|
||||
Installez-le. Il fonctionne aussitôt, sans réglage nécessaire.
|
||||
ublockorigin.com →🟢
|
||||
🔎
|
||||
DuckDuckGo · Brave Search · SearXNG
|
||||
🟢🟡 MoteursRemplace Google Search
|
||||
Des moteurs de recherche qui ne profilent pas chaque requête, contrairement à Google.
|
||||
DuckDuckGo et Brave Search ne conservent pas d'historique lié à votre identité. Startpage vous sert des résultats Google sans le pistage. SearXNG va plus loin : c'est un métamoteur auto-hébergeable, la souveraineté jusque dans la barre de recherche.
|
||||
Tout le monde. SearXNG vise ceux qui veulent héberger leur propre moteur.
|
||||
Dans les réglages de votre navigateur, changez le moteur par défaut pour DuckDuckGo, Brave Search ou Startpage.
|
||||
Pour SearXNG : utilisez une instance publique (annuaire sur searx.space) ou hébergez la vôtre (voir section 10).
|
||||
duckduckgo.com →search.brave.com →searx.space →🟢🟡
|
||||
RÉSEAU · DNS · 🟡
|
||||
DNS chiffré & Cloudflare
|
||||
Le DNS, c'est l'annuaire qui traduit un nom de site (exemple.com) en adresse machine. Problème : par défaut, c'est votre fournisseur d'accès qui gère cet annuaire, et il voit donc chaque site que vous visitez, même quand la page est en HTTPS. Chiffrer son DNS, c'est reprendre cette liste à son FAI. C'est un réglage discret mais parmi les plus efficaces.
|
||||
9
|
||||
Quad9
|
||||
🟢 DébutantAnti-maliciel🇨🇭 à but non lucratif
|
||||
Un résolveur DNS chiffré, suisse et à but non lucratif, qui bloque en plus les domaines malveillants connus.
|
||||
Juridiction suisse, pas de conservation de votre adresse IP, protection contre l'hameçonnage et les logiciels malveillants. Un excellent réglage « installer et oublier ».
|
||||
Tout le monde, en particulier ceux qui veulent la simplicité et une bonne juridiction.
|
||||
Sur Android : Réglages > Réseau > DNS privé > saisissez dns.quad9.net.
|
||||
Sur iPhone/Mac : installez le profil DoH depuis quad9.net. Sur PC : configurez le DNS-over-HTTPS dans le navigateur ou le système.
|
||||
quad9.net →🟢🟡
|
||||
DNS
|
||||
Mullvad DNS · NextDNS
|
||||
🟡 IntermédiaireFiltrage réseau
|
||||
Deux résolveurs chiffrés qui bloquent en plus pubs et traqueurs pour tout l'appareil.
|
||||
Mullvad DNS (Suède, no-log) est gratuit et propose des listes de blocage. NextDNS est très personnalisable, avec un tableau de bord et des profils par appareil, mais il est basé aux États-Unis et journalise par défaut (à désactiver dans les réglages).
|
||||
Ceux qui veulent un blocage des pubs à l'échelle du système, sans installer d'application par navigateur.
|
||||
Mullvad DNS : utilisez l'adresse indiquée sur mullvad.net/help/dns comme DNS privé (même méthode que Quad9).
|
||||
NextDNS : créez un profil sur nextdns.io, désactivez la journalisation, puis appliquez la configuration à vos appareils.
|
||||
mullvad.net/dns →nextdns.io →🟡
|
||||
CF
|
||||
Cloudflare 1.1.1.1 (à nuancer)
|
||||
🟢 DébutantRapide mais centralisé🇺🇸
|
||||
Le DNS chiffré 1.1.1.1 et l'application WARP de Cloudflare : rapides, gratuits, et un vrai progrès face au résolveur de votre FAI.
|
||||
C'est utile, avec un engagement de non-journalisation audité. Mais soyez lucide : Cloudflare se trouve déjà devant une immense partie du web (comme intermédiaire technique, il peut voir le trafic en clair vers ces sites). Y router aussi votre DNS revient à concentrer encore plus de vos traces chez une seule entreprise américaine soumise au droit américain.
|
||||
Bon pour un usage courant et le blocage des maliciels (1.1.1.1 for Families). À éviter ou à diversifier pour un usage sensible : préférez alors Quad9 ou Mullvad DNS.
|
||||
1.1.1.1 →🟢
|
||||
π
|
||||
Pi-hole · AdGuard Home
|
||||
🔴 AvancéBlocage tout le réseau🌍 · open-source
|
||||
Un bloqueur de publicités et de traqueurs qui protège tout votre réseau à la fois, installé sur un petit ordinateur comme un Raspberry Pi.
|
||||
Là où uBlock Origin protège un navigateur, Pi-hole filtre chaque appareil de la maison, y compris ceux où l'on ne peut pas installer d'extension : téléphones, téléviseurs connectés, objets connectés. Les pubs et traqueurs sont bloqués au niveau du DNS, avant même de se charger. AdGuard Home est une alternative à l'interface plus moderne.
|
||||
Les foyers qui veulent nettoyer tous leurs appareils d'un coup, et qui ont (ou veulent) un Raspberry Pi ou un petit serveur.
|
||||
Sur un Raspberry Pi (ou un conteneur), lancez l'installeur officiel de Pi-hole.
|
||||
Dans les réglages de votre box/routeur, indiquez l'adresse du Pi-hole comme serveur DNS.
|
||||
Désormais, tous les appareils connectés à votre réseau sont filtrés automatiquement.
|
||||
pi-hole.net →adguard home →🔴
|
||||
PARTIE 04 · 🟢
|
||||
VPN : la vérité
|
||||
Un VPN est utile, mais vendu avec beaucoup de mensonges. Voici ce qu'il protège vraiment.
|
||||
Ce qu'un VPN ne fait PAS
|
||||
Un VPN ne vous protège pas du scan côté client de Chat Control : celui-ci lit vos messages sur votre appareil, avant le chiffrement et donc avant le VPN. Ce n'est pas non plus une cape d'invisibilité : votre système et vos applications en voient plus que votre fournisseur de VPN.
|
||||
Ce qu'un VPN fait bien
|
||||
Il cache votre activité à votre fournisseur d'accès et masque votre adresse IP (donc votre localisation) aux sites visités. Utile contre la surveillance réseau, la géolocalisation et la censure. Une brique du puzzle, pas la solution.
|
||||
Mv
|
||||
Mullvad VPN
|
||||
🟢🟡🔴 Tous niveauxSans compte · cash/Monero🇸🇪 · open-source
|
||||
Un VPN qui masque votre adresse IP et chiffre votre trafic jusqu'à ses serveurs, sans jamais vous demander la moindre information personnelle.
|
||||
C'est l'étalon-or de la vie privée. Pas d'e-mail, pas de nom : à l'inscription, on vous attribue un simple numéro de compte. Politique « no-log » auditée, prix unique et honnête (5 € par mois, sans palier ni fausse promesse), et paiement possible en espèces ou en Monero. C'est précisément ce qui compte le jour où un État tente de couper l'accès aux VPN : un compte impossible à relier à votre identité, et un paiement que les banques ne peuvent pas bloquer.
|
||||
Tout le monde, du citoyen qui veut contourner une vérification d'identité au profil à haut risque. C'est le VPN à recommander en priorité.
|
||||
Allez sur mullvad.net. Si le site est bloqué dans votre pays, ouvrez-le via le navigateur Tor ou son adresse .onion. Cliquez sur « Générer un numéro de compte » : aucun e-mail ni nom n'est demandé. Notez ce numéro dans votre gestionnaire de mots de passe et ne le partagez jamais, c'est votre unique clé d'accès.
|
||||
Choisissez la durée et le mode de paiement. Le prix est fixe. Trois voies : carte bancaire (le plus simple si elle n'est pas bloquée), espèces envoyées par courrier, ou cryptomonnaie.
|
||||
Paiement anonyme en crypto : cliquez sur « Créer une adresse de paiement à usage unique ». Mullvad affiche une adresse (Bitcoin ou Monero) et un montant exact. Envoyez ce montant depuis votre portefeuille. Pour l'anonymat maximal, préférez Monero (confidentiel par défaut) ; Bitcoin est plus simple mais traçable. Voir la section 09 pour acheter de la crypto. Comptez environ 30 minutes de confirmation.
|
||||
Téléchargez l'application sur mullvad.net/download (via Tor si le site est filtré) et installez-la. Sur mobile, passez par l'App Store, Google Play, ou l'APK officiel si l'application a été retirée de votre magasin.
|
||||
Lancez l'application, entrez votre numéro de compte, puis connectez-vous à un serveur situé hors de votre pays, idéalement hors Union européenne (Suisse, États-Unis…). Activez le « kill switch » (qui coupe Internet si le VPN tombe) et le DNS de Mullvad.
|
||||
mullvad.net →🟢🟡🔴
|
||||
Pv
|
||||
Proton VPN
|
||||
🟢 DébutantOffre gratuite honnête🇨🇭 · open-source
|
||||
Le VPN de l'écosystème Proton, avec une véritable offre gratuite (rare et honnête).
|
||||
Basé en Suisse, audité, open-source. Son offre gratuite est sans publicité, sans revente de données et sans limite de volume (le nombre de pays est simplement réduit). C'est la porte d'entrée idéale pour un débutant, surtout si vous utilisez déjà Proton Mail.
|
||||
Débutants, et toute personne déjà dans l'écosystème Proton qui veut un seul compte pour tout.
|
||||
Rendez-vous sur protonvpn.com et connectez-vous avec votre compte Proton (ou créez-en un).
|
||||
Installez l'application (Windows, macOS, Linux, Android, iOS).
|
||||
Utilisez « Quick Connect » ou choisissez un pays, puis activez le kill switch et NetShield (blocage des pubs et traqueurs).
|
||||
protonvpn.com →🟢
|
||||
iv
|
||||
IVPN
|
||||
🟡🔴 Interm. / AvancéSans e-mail · no-log🇬🇮 · open-source
|
||||
Un petit fournisseur de VPN particulièrement rigoureux et transparent, sur le même modèle que Mullvad.
|
||||
Politique no-log auditée, pas d'e-mail obligatoire, paiement en crypto ou en espèces. Il propose même des options anti-traçage (blocage des trackers au niveau réseau) et une inscription sans aucun identifiant.
|
||||
Une alternative solide à Mullvad, pour les profils pseudonymes et à haut risque qui veulent diversifier.
|
||||
Sur ivpn.net, générez un compte (un identifiant est créé pour vous, sans e-mail).
|
||||
Payez en cryptomonnaie ou en espèces, comme pour Mullvad.
|
||||
Installez l'application, connectez-vous hors UE, activez le kill switch et le protocole WireGuard.
|
||||
ivpn.net →🟡🔴
|
||||
CENSURE · IDENTITÉ NUMÉRIQUE · 🟡
|
||||
Censure & vérification d'identité
|
||||
Le même prétexte (« protéger les mineurs ») sert à imposer la vérification d'identité pour accéder à Internet. C'est la fin programmée de l'anonymat en ligne, donc une forme de censure. Voici l'état des lieux, puis comment y échapper légalement.
|
||||
Où en est-on (2025-2026)
|
||||
France. La loi SREN (mai 2024) confie à l'Arcom la vérification d'âge ; depuis avril 2025, les sites concernés doivent la mettre en place. Un décret imposant la vérification d'âge a été validé par le Conseil d'État le 15 juillet 2025, et une proposition interdisant les réseaux sociaux aux moins de 15 ans a été votée à l'Assemblée le 26 janvier 2026, ce qui suppose de vérifier l'âge, donc l'identité, de tout le monde.
|
||||
Royaume-Uni. Depuis le 25 juillet 2025, l'Online Safety Act impose une vérification d'âge « hautement efficace » (pièce d'identité, carte bancaire, estimation faciale). Résultat immédiat : les inscriptions VPN ont bondi de plus de 1 400 % en quelques heures. La Chambre des Lords a même débattu, début 2026, d'un âge minimum pour utiliser un VPN.
|
||||
Union européenne. La Commission a présenté une application de vérification d'âge (avril 2025), pilotée dans cinq pays, pensée pour s'articuler avec le portefeuille d'identité numérique européen (EUDI Wallet, eIDAS 2) que chaque État doit proposer d'ici fin 2026. L'EFF et EDRi alertent : la preuve « à divulgation nulle » n'est qu'optionnelle, et cela construit une infrastructure d'identité en ligne bien au-delà de la protection de l'enfance.
|
||||
Y échapper légalement
|
||||
La parade tient en une idée : prendre une adresse IP dans un pays qui n'impose pas ces contrôles, et se procurer les outils de façon anonyme, même si un jour ils sont bloqués.
|
||||
Un VPN hors juridiction. Connectez-vous à un serveur situé hors du pays qui filtre (idéalement hors UE et hors « 14 Eyes »), avec une politique no-log auditée. C'est ce qui vous rend l'accès aux réseaux qui exigent une pièce d'identité.
|
||||
Un paiement anonyme. Payez le VPN en cryptomonnaie ou en espèces (Mullvad, IVPN, Proton l'acceptent). Crucial : si un jour les VPN sont « interdits », les processeurs de paiement pourraient refuser les cartes ; le cash et la crypto resteront les seuls moyens.
|
||||
Tor pour récupérer un VPN bloqué. Si le site du fournisseur devient inaccessible depuis votre pays, passez par le navigateur Tor (ou une adresse .onion) pour télécharger le client. Une fois installé, le VPN est bien plus rapide pour l'usage quotidien.
|
||||
Changer le pays du compte. Un compte Apple/Google réglé sur un autre pays fait réapparaître les applications retirées de votre magasin local.
|
||||
Le droit bouge, vérifiez
|
||||
Contourner une restriction géographique est légal dans la plupart des pays européens, mais le terrain change vite : l'Utah (États-Unis) a criminalisé le contournement par VPN, et les Lords britanniques envisagent d'encadrer l'usage des VPN. Vérifiez la loi de votre pays avant de vous reposer sur une méthode. Ce guide vise la protection de la vie privée, pas la dissimulation d'un délit.
|
||||
PARTIE 05 · REPRENDRE LE CONTRÔLE · 🟡
|
||||
Quitter Google
|
||||
Google, c'est un compte unique qui connaît vos e-mails, votre agenda, vos fichiers, vos déplacements et vos recherches. On remplace la suite entière.
|
||||
Google
|
||||
Proton
|
||||
Autres
|
||||
Gmail
|
||||
Proton Mail
|
||||
Tuta, mailbox.org
|
||||
Google Drive
|
||||
Proton Drive
|
||||
Nextcloud, Cryptomator
|
||||
Google Calendar
|
||||
Proton Calendar
|
||||
Tuta Calendar
|
||||
Google Password
|
||||
Proton Pass
|
||||
Bitwarden, KeePassXC
|
||||
Google One VPN
|
||||
Proton VPN
|
||||
Mullvad, IVPN
|
||||
L'intérêt de Proton (Suisse, à but non lucratif via une fondation, open-source) : un seul compte remplace tout Google, avec chiffrement de bout en bout et une juridiction hors « Five Eyes ». On peut migrer progressivement, service par service. Pour qui préfère éviter de recentraliser chez un seul acteur, chaque ligne du tableau a une alternative indépendante.
|
||||
PARTIE 06 · 🟡
|
||||
Stockage chiffré
|
||||
Pd
|
||||
Proton Drive
|
||||
🟢 DébutantRemplace Google Drive / iCloud🇨🇭 · E2EE
|
||||
Un cloud chiffré de bout en bout pour vos fichiers et vos photos, intégré à Proton.
|
||||
Contrairement à Google Drive ou iCloud, Proton ne peut pas lire vos fichiers : ils sont chiffrés avant de quitter votre appareil. Juridiction suisse, même compte que Proton Mail.
|
||||
Tout le monde, comme remplacement direct et simple du cloud de Google ou d'Apple.
|
||||
Activez Proton Drive dans votre compte Proton et installez l'application.
|
||||
Activez la sauvegarde automatique de vos photos, puis désactivez celle de Google/Apple.
|
||||
proton.me/drive →🟢🟡
|
||||
Cy
|
||||
Cryptomator
|
||||
🟡 IntermédiaireChiffre avant l'envoi🇩🇪 · open-source
|
||||
Un coffre qui chiffre vos fichiers avant de les envoyer sur n'importe quel cloud, même Google Drive ou Dropbox.
|
||||
L'astuce parfaite si vous devez garder un cloud existant : vous conservez le service pratique, mais le fournisseur ne voit plus que du charabia chiffré. Vous gardez seul la clé.
|
||||
Ceux qui ne peuvent pas quitter un cloud grand public tout de suite, mais veulent protéger leurs fichiers dès maintenant.
|
||||
Installez Cryptomator (PC et mobile) depuis cryptomator.org.
|
||||
Créez un « coffre » dans le dossier synchronisé de votre cloud, choisissez un mot de passe fort.
|
||||
Placez vos fichiers dans le coffre : ils sont chiffrés puis synchronisés automatiquement.
|
||||
cryptomator.org →🟡🔴
|
||||
Nc
|
||||
Nextcloud
|
||||
🔴 AvancéVotre propre cloud🌍 · open-source
|
||||
Votre propre nuage (fichiers, agenda, contacts, notes) hébergé sur votre serveur.
|
||||
Le degré ultime de contrôle : vos données ne quittent jamais votre matériel. Détaillé dans la section 10 (Auto-hébergement).
|
||||
Les auto-hébergeurs. Voir la fiche complète en section 10.
|
||||
→ Section 10nextcloud.com →🔴
|
||||
PARTIE 07 · 🟡
|
||||
Gestionnaire de mots de passe
|
||||
Un mot de passe unique et fort par service : c'est la base de toute la sécurité qui suit. Arrêtez le carnet, arrêtez le même mot de passe partout.
|
||||
Bw
|
||||
Bitwarden
|
||||
🟢 DébutantLe meilleur pour débuter🌍 · open-source
|
||||
Un coffre-fort à mots de passe qui les mémorise, les génère et les remplit pour vous, sur tous vos appareils.
|
||||
Gratuit, audité, multiplateforme, et chiffré de bout en bout. Vous n'avez plus qu'un seul mot de passe fort à retenir. Il est même auto-hébergeable (via Vaultwarden) pour ne dépendre de personne.
|
||||
Tout le monde. C'est le point de départ de toute la sécurité : un mot de passe unique et fort par service.
|
||||
Créez un compte sur bitwarden.com et choisissez un mot de passe maître long (une phrase). Ne l'oubliez jamais : il n'est pas récupérable.
|
||||
Installez l'extension de navigateur et l'application mobile.
|
||||
Au fil de vos connexions, laissez Bitwarden enregistrer puis remplacer vos anciens mots de passe par des mots de passe générés.
|
||||
bitwarden.com →🟢🟡🔴
|
||||
Kp
|
||||
KeePassXC
|
||||
🔴 Avancé100% local🌍 · open-source
|
||||
Un coffre-fort local : un fichier chiffré que vous seul détenez, sans aucun cloud ni compte.
|
||||
Rien ne quitte votre appareil, sauf si vous décidez de synchroniser le fichier vous-même (par exemple via Cryptomator). C'est le choix des puristes qui ne veulent aucun intermédiaire.
|
||||
Les profils avancés qui veulent le contrôle total et zéro dépendance en ligne.
|
||||
Installez KeePassXC (PC) et une application compatible sur mobile (KeePassDX sur Android, Strongbox sur iOS).
|
||||
Créez un fichier de base de données protégé par un mot de passe maître.
|
||||
Pour l'avoir sur tous vos appareils, synchronisez ce fichier via un cloud chiffré.
|
||||
keepassxc.org →🔴
|
||||
Pp
|
||||
Proton Pass
|
||||
🟢 DébutantAvec alias e-mail🇨🇭 · open-source
|
||||
Le gestionnaire de mots de passe de l'écosystème Proton, avec des alias e-mail jetables intégrés.
|
||||
Idéal si vous êtes déjà chez Proton : un seul compte pour le mail, le cloud et les mots de passe. Ses alias e-mail vous évitent de donner votre vraie adresse partout.
|
||||
Ceux qui veulent tout regrouper chez Proton.
|
||||
proton.me/pass →🟢🟡
|
||||
SÉCURITÉ · 2FA · 🟡
|
||||
Double authentification & clés matérielles
|
||||
Un mot de passe, même fort, peut fuiter. La double authentification (2FA) ajoute une seconde preuve à la connexion : même si votre mot de passe est volé, le compte reste fermé. C'est indispensable sur vos comptes sensibles (e-mail, gestionnaire de mots de passe, réseaux).
|
||||
Évitez le 2FA par SMS
|
||||
Le code reçu par SMS est le maillon faible : il est vulnérable au « SIM-swap » (un attaquant fait transférer votre numéro) et à l'interception via le réseau téléphonique (SS7). Des études estiment que la majorité des tentatives de SIM-swap réussissent. Utilisez le SMS seulement en dernier recours, jamais comme protection principale.
|
||||
Yk
|
||||
YubiKey · Nitrokey
|
||||
🟡🔴 Interm. / AvancéClé matérielle FIDO2
|
||||
Une petite clé physique (format USB, souvent avec NFC) qui prouve votre identité d'un simple contact du doigt.
|
||||
C'est la 2FA la plus solide : elle résiste à l'hameçonnage, car la clé vérifie l'adresse réelle du site avant de répondre. Nitrokey est l'alternative à matériel et micrologiciel entièrement ouverts. Un faux site ne peut pas vous piéger.
|
||||
Ceux qui veulent le maximum de sécurité sur leurs comptes clés. Achetez-en deux (une principale, une de secours).
|
||||
Procurez-vous deux clés sur yubico.com ou nitrokey.com.
|
||||
Dans les réglages de sécurité de chaque compte (e-mail, gestionnaire de mots de passe…), ajoutez une « clé de sécurité » ou « passkey ». Enregistrez vos deux clés.
|
||||
Gardez la clé de secours dans un lieu sûr, séparé.
|
||||
yubico.com →nitrokey.com →🟡🔴
|
||||
Ae
|
||||
Aegis · Ente Auth
|
||||
🟢 DébutantCodes TOTP🌍 · open-source
|
||||
Des applications qui génèrent les codes à six chiffres qui changent toutes les 30 secondes, en remplacement de Google Authenticator.
|
||||
Aegis (Android) garde vos codes dans un coffre chiffré local, sans cloud. Ente Auth ajoute une synchronisation chiffrée de bout en bout entre appareils, et a été audité. Bien plus sûrs que le SMS, et gratuits.
|
||||
Tout le monde : c'est le niveau de 2FA à adopter par défaut, avant les clés matérielles.
|
||||
Installez Aegis (F-Droid) ou Ente Auth (toutes plateformes).
|
||||
Sur chaque compte, activez la 2FA « application d'authentification » et scannez le QR code affiché.
|
||||
Notez les codes de secours sur papier et faites une sauvegarde chiffrée de votre coffre.
|
||||
getaegis.app →ente.io/auth →🟢🟡
|
||||
PARTIE 08 · 🟡
|
||||
Réseaux sociaux décentralisés
|
||||
Instagram, X et Facebook appartiennent à des entreprises qui vous profilent et peuvent vous censurer ou vous supprimer du jour au lendemain. Le « fédiverse » propose des réseaux appartenant à leurs utilisateurs.
|
||||
Ma
|
||||
Mastodon
|
||||
🟢 DébutantRemplace X / Twitter🌍 · open-source
|
||||
L'alternative à X/Twitter : un réseau social fait de milliers de serveurs indépendants qui communiquent entre eux (le « fédiverse »).
|
||||
Pas de patron unique, pas d'algorithme qui décide à votre place, pas de bannissement arbitraire par une seule entreprise. Le fil est chronologique, sans publicité ni manipulation.
|
||||
Tout le monde. Idéal pour les comptes d'information qui veulent une présence qu'aucune entreprise ne peut couper.
|
||||
Sur joinmastodon.org, choisissez un serveur (ou « instance ») qui vous correspond, puis créez un compte.
|
||||
Installez une application (l'officielle, ou Ivory, Tusky…) et suivez des comptes : vous voyez tout le fédiverse, quel que soit leur serveur.
|
||||
joinmastodon.org →🟢🟡
|
||||
No
|
||||
Nostr
|
||||
🟡🔴Incensurable🌍 · protocole ouvert
|
||||
Pas une application mais un protocole : un réseau social où votre identité est une simple paire de clés, comme pour Bitcoin.
|
||||
Zéro e-mail, zéro mot de passe, aucun compte qu'on puisse supprimer. Vos messages voyagent sur des relais décentralisés et vous possédez vraiment votre audience : personne ne peut vous bannir durablement. Vous pouvez même recevoir des pourboires en Bitcoin (les « zaps ») et changer d'application quand vous voulez sans perdre vos abonnés.
|
||||
Comptes d'information et créateurs qui craignent la censure de plateforme et veulent une présence vraiment à eux.
|
||||
Installez un client : Damus (iPhone), Amethyst ou Primal (Android), ou une version web.
|
||||
L'application génère votre paire de clés. Sauvegardez votre clé privée (nsec) comme une phrase de récupération Bitcoin : sur papier, jamais partagée.
|
||||
Créez votre profil, suivez des gens, activez les zaps pour recevoir des sats.
|
||||
nostr.com →🟡🔴
|
||||
El
|
||||
Matrix / Element
|
||||
🟡🔴Remplace Discord / Slack🌍 · fédéré · E2EE
|
||||
Une messagerie de communautés, dans l'esprit de Discord ou Slack, mais fédérée et chiffrable de bout en bout. Element est l'application, Matrix le réseau.
|
||||
Vous pouvez héberger votre propre serveur (Synapse) et contrôler vos données. Parfait pour un collectif, une rédaction, une association qui veut son espace indépendant.
|
||||
Communautés et groupes. Voir la nuance sur les métadonnées ci-dessous.
|
||||
Installez Element (element.io) et créez un compte sur un serveur public, ou sur le vôtre (section 10).
|
||||
Rejoignez ou créez des salons, et activez le chiffrement de bout en bout pour les conversations privées.
|
||||
element.io →🟡🔴
|
||||
Pi
|
||||
Pixelfed · PeerTube · Lemmy
|
||||
🟡Insta · YouTube · Reddit🌍 · open-source
|
||||
Les cousins fédérés d'Instagram (Pixelfed), de YouTube (PeerTube) et de Reddit (Lemmy).
|
||||
Même logique que Mastodon : pas de propriétaire unique, pas d'algorithme publicitaire, pas de traçage. Vous publiez photos, vidéos ou discussions sans nourrir une machine à profiler.
|
||||
Ceux qui veulent quitter Instagram, YouTube ou Reddit sans renoncer au format.
|
||||
pixelfed.org →peertube →lemmy →🟡
|
||||
Nuance métadonnées
|
||||
Matrix chiffre bien le contenu, mais en fédération, les serveurs participants voient l'appartenance aux salons et l'horodatage des messages. Pour un usage très sensible, préférez SimpleX/Session ou auto-hébergez votre serveur.
|
||||
PARTIE 09 · 🟡
|
||||
Souveraineté financière
|
||||
L'argent aussi est surveillé et censurable. Des comptes de militants ont été gelés, des dons bloqués, chaque paiement par carte est tracé. La vie privée financière fait partie de la souveraineté.
|
||||
₿
|
||||
Bitcoin (auto-conservation)
|
||||
🟡🔴Résistant à la censure
|
||||
Une monnaie numérique que vous détenez vous-même, sans passer par une banque.
|
||||
En auto-conservation (vos clés dans un portefeuille que vous seul contrôlez), personne ne peut geler ni bloquer vos fonds. Sa confidentialité n'est pas parfaite (le registre est public), mais il échappe à la censure bancaire, utile le jour où un paiement est refusé.
|
||||
Se prémunir contre le gel de compte ou la dé-bancarisation, et payer anonymement un service comme un VPN.
|
||||
Procurez-vous des bitcoins (plateforme d'échange, ou entre particuliers).
|
||||
Transférez-les aussitôt vers un portefeuille que vous contrôlez : un portefeuille matériel (Trezor, ColdCard) ou une application libre. Ne les laissez pas sur la plateforme.
|
||||
Sauvegardez votre phrase de récupération sur papier, jamais en photo ni en ligne.
|
||||
bitcoin.org →🟡🔴
|
||||
ɱ
|
||||
Monero (XMR)
|
||||
🔴 AvancéConfidentiel par défaut
|
||||
La cryptomonnaie de la vie privée : montants, expéditeur et destinataire sont masqués par défaut.
|
||||
C'est l'exact opposé de Bitcoin sur ce point : les transactions sont confidentielles par conception. C'est le meilleur moyen de payer un service sans laisser de trace reliable à vous.
|
||||
Ceux qui veulent le maximum de confidentialité financière. Note : Monero est déjà retiré de plusieurs plateformes en Europe, il faut souvent passer par un échange décentralisé.
|
||||
Installez un portefeuille (l'officiel sur getmonero.org, ou Cake Wallet sur mobile).
|
||||
Obtenez des XMR via un service d'échange décentralisé (par exemple un « swap » depuis une autre crypto).
|
||||
Sauvegardez votre phrase de récupération sur papier.
|
||||
getmonero.org →🔴
|
||||
Légalité & prudence
|
||||
La confidentialité financière est légale. Ne la confondez pas avec l'évasion fiscale : déclarez ce qui doit l'être. Les cryptomonnaies sont volatiles et les arnaques nombreuses ; ne placez jamais ce que vous ne pouvez pas perdre, et formez-vous avant d'agir.
|
||||
ANGLE MORT · LA FOLIE DE L'IA · 🟡
|
||||
L'IA conversationnelle
|
||||
Pendant qu'on se bat pour chiffrer nos messages, des centaines de millions de gens confient leurs pensées les plus intimes à ChatGPT, Gemini ou Copilot, sur des serveurs américains. C'est l'angle mort de toute cette histoire. Une pure folie.
|
||||
Ce que vous tapez dans une IA cloud est une confession écrite, horodatée et stockée. Pire qu'un message : vous y déballez vos angoisses, votre santé, vos finances, vos secrets professionnels, souvent plus honnêtement qu'à un proche. Et contrairement à une messagerie chiffrée, tout est lisible en clair côté serveur.
|
||||
Ce que disent les faits (2025-2026)
|
||||
Les offres grand public s'entraînent sur vos données par défaut. En janvier 2026, un juge américain a ordonné à OpenAI de produire 20 millions de conversations ChatGPT comme preuves : les utilisateurs concernés n'ont été ni prévenus ni consultés. Supprimer une conversation ne garantit pas son effacement. Et lors d'une fuite, 47 000 conversations exposées contenaient e-mails, numéros et détails intimes ré-identifiables.
|
||||
La parade : l'IA locale
|
||||
Ai
|
||||
Ollama · LM Studio · Jan · GPT4All
|
||||
🟡🔴Remplace ChatGPT🌍 · 100% local
|
||||
Des logiciels pour faire tourner un modèle d'intelligence artificielle directement sur votre ordinateur.
|
||||
Vos questions ne quittent jamais l'appareil : aucun serveur, aucun compte, aucune télémétrie, et ça marche hors ligne. La qualité dépend de votre matériel, mais rien ne fuit. Ollama et LM Studio sont les plus simples ; Jan et GPT4All visent la vie privée maximale.
|
||||
Tous ceux qui utilisent l'IA pour des sujets un tant soit peu personnels et ne veulent pas les confier à un serveur américain.
|
||||
Pour débuter en douceur, installez LM Studio ou Jan (interface graphique, comme une application classique).
|
||||
Téléchargez un modèle proposé dans l'application (par exemple Llama ou Mistral), adapté à la puissance de votre machine.
|
||||
Discutez : tout se passe en local. Ollama est la version en ligne de commande pour les plus techniques.
|
||||
lmstudio.ai →jan.ai →ollama.com →🟡🔴
|
||||
☁
|
||||
Duck.ai · Lumo (Proton)
|
||||
🟢🟡Cloud orienté vie privée
|
||||
Des accès à l'IA dans le cloud, mais conçus pour la vie privée, quand une IA locale n'est pas possible.
|
||||
Duck.ai (DuckDuckGo) donne un accès anonymisé à plusieurs modèles. Lumo (Proton) chiffre les échanges et ne s'en sert pas pour entraîner. Bien mieux que ChatGPT grand public, mais cela reste un serveur distant.
|
||||
Ceux qui veulent la commodité du cloud sans nourrir les géants. Gardez-y les sujets non sensibles.
|
||||
duck.ai →lumo →🟢🟡
|
||||
La règle simple
|
||||
Ne collez jamais dans une IA cloud ce que vous ne diriez pas à un inconnu qui enregistre : identité, santé, mots de passe, secrets professionnels, aveux. Pour tout le reste de sensible, une IA locale.
|
||||
REMPLACER LE RESTE · 🟡
|
||||
La boîte à outils du quotidien
|
||||
Google et Apple ne sont pas que des e-mails : cartes, notes, agenda, photos, visio, tout est relié à votre identité. Voici de quoi remplacer chaque brique, une par une, sans rien perdre en confort.
|
||||
Om
|
||||
Organic Maps · OsmAnd
|
||||
🟢 DébutantRemplace Google Maps🌍 · open-source
|
||||
Des applications de cartes et de navigation, fondées sur OpenStreetMap, qui fonctionnent hors ligne.
|
||||
Google Maps enregistre chacun de vos trajets et bâtit un historique de lieux d'une précision redoutable. Organic Maps ne trace rien et n'affiche aucune publicité ; OsmAnd vise les utilisateurs avancés (randonnée, courbes de niveau, navigation détaillée).
|
||||
Tout le monde. Le trafic en temps réel et les transports sont moins complets que chez Google, mais la navigation quotidienne est excellente.
|
||||
Installez depuis F-Droid, l'App Store ou Google Play.
|
||||
Téléchargez à l'avance les cartes des régions qui vous intéressent pour l'usage hors ligne.
|
||||
organicmaps.app →osmand.net →🟢
|
||||
No
|
||||
Standard Notes · Joplin · CryptPad
|
||||
🟢🟡Remplace Docs / Notion🌍 · open-source
|
||||
Des notes et documents chiffrés de bout en bout, pour remplacer Google Docs, Notion ou Evernote.
|
||||
Standard Notes (par Proton) chiffre vos notes par défaut. Joplin gère des carnets en Markdown avec chiffrement optionnel et la synchronisation de votre choix. CryptPad (français) offre l'équivalent de Google Docs en temps réel, entièrement chiffré.
|
||||
Tout le monde pour les notes ; CryptPad pour la collaboration à plusieurs.
|
||||
Standard Notes / Joplin : créez un compte, installez l'application, et activez le chiffrement (par défaut sur Standard Notes, à activer dans Joplin).
|
||||
CryptPad : utilisez cryptpad.fr ou une autre instance, aucun compte requis pour commencer.
|
||||
standardnotes.com →joplinapp.org →cryptpad.org →🟢🟡
|
||||
Vi
|
||||
Jitsi Meet · Element Call
|
||||
🟢🟡Remplace Zoom / Meet🌍 · open-source
|
||||
Des outils de visioconférence sans compte ni installation lourde, pour remplacer Zoom, Google Meet ou Teams.
|
||||
Jitsi Meet se lance dans le navigateur, sans compte, et peut être auto-hébergé (c'est aussi la base de Brave Talk). Element Call est chiffré de bout en bout et fédéré via Matrix. Pour un appel simple et totalement chiffré, les appels Signal font aussi le travail.
|
||||
Tout le monde. Sur un serveur Jitsi public, les appels à plusieurs sont chiffrés en transport ; le chiffrement de bout en bout est en option.
|
||||
Jitsi : allez sur meet.jit.si, créez un nom de salon, partagez le lien. Rien à installer.
|
||||
Element Call : depuis l'application Element (Matrix), lancez un appel dans un salon.
|
||||
jitsi.org →element.io/element-call →🟢🟡
|
||||
Ph
|
||||
Ente · Immich
|
||||
🟡🔴Remplace Google Photos🌍 · open-source
|
||||
Deux façons de garder vos photos sans les confier à Google ou Apple.
|
||||
Ente est un service géré, chiffré de bout en bout, avec reconnaissance et recherche faites sur l'appareil : simple et sûr. Immich est un clone de Google Photos que vous hébergez vous-même (recherche par visages et objets incluse), à réserver à ceux qui savent gérer un serveur.
|
||||
Ente pour tous ; Immich pour les auto-hébergeurs. Attention : Immich n'est pas chiffré de bout en bout, sa sécurité dépend de votre serveur.
|
||||
Ente : installez l'application, créez un compte, activez la sauvegarde automatique de votre pellicule.
|
||||
Immich : déployez-le sur votre serveur (voir section 10) via Docker, puis connectez l'application mobile.
|
||||
ente.io →immich.app →🟡🔴
|
||||
@
|
||||
SimpleLogin · addy.io
|
||||
🟢🟡Alias e-mail🌍 · open-source
|
||||
Des alias e-mail jetables : une adresse unique par site, qui redirige vers votre vraie boîte sans jamais la révéler.
|
||||
Vous ne donnez plus votre vraie adresse partout. Si un site est piraté ou vous spamme, vous coupez l'alias correspondant, et vous savez immédiatement qui a fait fuiter vos données. SimpleLogin est intégré à Proton ; addy.io propose des alias illimités dès l'offre gratuite.
|
||||
Tout le monde, et particulièrement les comptes pseudonymes qui veulent cloisonner leurs inscriptions.
|
||||
Créez un compte, installez l'extension de navigateur.
|
||||
À chaque inscription, générez un nouvel alias au lieu de saisir votre vraie adresse.
|
||||
simplelogin.io →addy.io →🟢🟡
|
||||
Bk
|
||||
Sauvegardes chiffrées
|
||||
🟡🔴Cryptomator · restic🌍 · open-source
|
||||
De quoi sauvegarder votre téléphone et votre ordinateur en chiffrant les données avant qu'elles ne partent dans un cloud.
|
||||
Les sauvegardes iCloud/Google sont souvent accessibles au fournisseur, donc à la justice. Cryptomator chiffre vos fichiers avant de les envoyer sur n'importe quel cloud ; restic fait des sauvegardes chiffrées automatiques ; Proton Drive est chiffré de bout en bout. Sur iPhone, activez la « Protection avancée des données » pour chiffrer vos sauvegardes iCloud.
|
||||
Tout le monde devrait chiffrer ses sauvegardes. Règle d'or : deux copies chiffrées, sur deux supports, en deux lieux.
|
||||
iPhone : Réglages > votre nom > iCloud > Protection avancée des données : activez-la.
|
||||
PC : installez Cryptomator, créez un coffre, placez-y vos fichiers avant de synchroniser vers le cloud.
|
||||
cryptomator.org →restic.net →🟡🔴
|
||||
RG
|
||||
Effacer ses données (RGPD)
|
||||
🟡Courtiers en données
|
||||
Faire retirer vos informations personnelles des « courtiers en données » qui les collectent et les revendent.
|
||||
En Europe, le RGPD (article 17) vous donne un droit à l'effacement : vous pouvez exiger, gratuitement, qu'une entreprise supprime vos données, en principe sous un mois. Des services comme Incogni automatisent ces demandes en masse, moyennant paiement.
|
||||
Ceux qui veulent réduire leur exposition. Attention : les données réapparaissent avec le temps, et cela ne touche ni les registres publics ni les réseaux sociaux. Le faire soi-même est gratuit mais fastidieux.
|
||||
Pour une demande manuelle : écrivez au courtier en invoquant l'article 17 du RGPD et demandez la suppression.
|
||||
Pour automatiser : souscrivez à un service de retrait et laissez-le envoyer les demandes en votre nom.
|
||||
cnil.fr →incogni.com →🟡
|
||||
PARTIE 10 · AUTODÉFENSE AVANCÉE · 🔴
|
||||
Auto-hébergement
|
||||
Le niveau ultime de souveraineté : héberger vos services vous-même. Vos données vivent sur votre matériel, sous la juridiction que vous choisissez.
|
||||
Yh
|
||||
YunoHost · Umbrel · Start9
|
||||
🔴 AvancéServeur clé en main🌍 · open-source
|
||||
Des systèmes qui transforment un vieux PC ou un Raspberry Pi en serveur personnel, avec un catalogue d'applications à installer en quelques clics.
|
||||
L'auto-hébergement fait peur, mais ces outils font le gros du travail. YunoHost gère même votre propre e-mail et une authentification unique. Umbrel offre un magasin de plus de 300 applications, très prisé des amateurs de vie privée et de Bitcoin. Start9 chiffre les sauvegardes et donne une adresse Tor à chaque service par défaut.
|
||||
Les profils avancés prêts à consacrer un après-midi à monter leur propre cloud. C'est la parade structurelle la plus radicale à Chat Control.
|
||||
Récupérez un vieil ordinateur, un Raspberry Pi, ou louez un petit serveur (VPS) dans un pays protecteur.
|
||||
Installez YunoHost, Umbrel ou Start9 en suivant leur guide officiel.
|
||||
Depuis le catalogue, installez Nextcloud, un serveur Matrix, une galerie photo, etc.
|
||||
Accédez-y en privé avec Tailscale (fiche ci-dessous) plutôt que d'exposer le serveur au public.
|
||||
yunohost.org →umbrel.com →start9.com →🔴
|
||||
Nc
|
||||
Nextcloud
|
||||
🔴 AvancéRemplace toute la suite Google🇩🇪 · open-source
|
||||
Votre propre nuage : fichiers, agenda, contacts, photos, notes et documents collaboratifs, sur votre serveur.
|
||||
C'est le remplacement complet de Google Drive, Agenda et Contacts, mais chez vous. Vous décidez de la juridiction et personne d'autre n'a la main sur vos fichiers. À savoir : c'est la confidentialité « votre serveur », pas un chiffrement de bout en bout par défaut ; vous devez le maintenir à jour.
|
||||
Les auto-hébergeurs. Le plus simple est de l'installer via YunoHost ou Umbrel.
|
||||
nextcloud.com →🔴
|
||||
Mx
|
||||
Matrix Synapse
|
||||
🔴 AvancéVotre serveur de chat🌍 · open-source
|
||||
Votre propre serveur de messagerie Matrix, sur lequel tournent Element et les appels chiffrés.
|
||||
En hébergeant le serveur, vous contrôlez aussi les métadonnées (qui parle à qui, quand), le point faible de Matrix en fédération. C'est la messagerie communautaire dont vous êtes le seul maître.
|
||||
Communautés, collectifs, rédactions qui veulent leur espace fédéré indépendant.
|
||||
element.io/server →🔴
|
||||
Ts
|
||||
Tailscale · Headscale · WireGuard
|
||||
🔴 AvancéAccès privé à son serveur🌍 · open-source
|
||||
Un réseau privé chiffré qui relie vos appareils à votre serveur, sans jamais l'exposer sur l'Internet public.
|
||||
Au lieu d'ouvrir des ports (et de vous faire attaquer), Tailscale crée un tunnel WireGuard entre vos seuls appareils autorisés : votre serveur n'a aucune surface d'attaque publique. Headscale est la version que vous hébergez vous-même, pour ne dépendre d'aucun tiers. WireGuard seul offre le contrôle total, au prix d'une configuration manuelle.
|
||||
Tout auto-hébergeur. C'est la façon sûre d'atteindre son Nextcloud ou ses photos depuis l'extérieur.
|
||||
Installez Tailscale sur votre serveur et sur votre téléphone/PC, connectez-les au même compte.
|
||||
Vos appareils se voient aussitôt sur un réseau privé ; accédez à votre serveur par son adresse Tailscale.
|
||||
Pour zéro dépendance, remplacez le serveur de coordination par Headscale, auto-hébergé.
|
||||
tailscale.com →headscale.net →🔴
|
||||
www
|
||||
Votre propre site web
|
||||
🟡🔴Résister au bannissement
|
||||
Un site à vous, sur votre nom de domaine, où votre contenu ne dépend d'aucune plateforme.
|
||||
Le jour où un réseau supprime ou suspend votre compte, tout votre travail disparaît. Un site personnel est votre point d'ancrage : vos articles y restent, archivés et à jour, quoi qu'il arrive. C'est exactement ce que font les comptes d'information prudents. Un site statique (généré par Hugo, Astro ou Jekyll) suffit et se publie presque gratuitement ; pour un blog dynamique, un WordPress ou un Ghost auto-hébergé fait l'affaire.
|
||||
Comptes d'information, créateurs, militants, quiconque publie et craint la censure de plateforme.
|
||||
Achetez un nom de domaine (idéalement chez un registraire respectueux de la vie privée).
|
||||
Choisissez un générateur de site statique (Astro, Hugo) et hébergez le résultat, ou installez WordPress/Ghost sur votre serveur.
|
||||
Renvoyez-y systématiquement depuis vos réseaux : votre audience vous retrouve même si un compte tombe.
|
||||
astro.build →ghost.org →🟡🔴
|
||||
Le point juridiction
|
||||
Auto-héberger, c'est aussi choisir où vivent vos données. Un serveur chez vous ou dans un pays protecteur échappe aux obligations de scan imposées à une grande plateforme. C'est la parade structurelle la plus radicale à Chat Control.
|
||||
PARTIE 11 · 🔴
|
||||
Tor
|
||||
Le réseau qui sépare qui vous êtes de ce que vous faites en ligne.
|
||||
Tor
|
||||
Tor Browser
|
||||
🔴 AvancéAnonymat réseau🌍 · open-source
|
||||
Un réseau qui sépare qui vous êtes de ce que vous faites en ligne, accessible via le Tor Browser.
|
||||
Votre trafic rebondit à travers plusieurs relais chiffrés dans le monde : aucun ne connaît à la fois votre identité et votre destination. C'est l'outil des journalistes et des sources, et le moyen d'atteindre un site bloqué (par exemple pour télécharger un VPN censuré).
|
||||
Profils à haut risque, et quiconque doit dissocier son activité de son adresse IP réelle. À utiliser ponctuellement, pas comme navigateur de tous les jours.
|
||||
Téléchargez le Tor Browser sur torproject.org (Windows, macOS, Linux, Android).
|
||||
Lancez-le et cliquez « Se connecter ». Si Tor est bloqué dans votre pays, activez un « pont » (bridge).
|
||||
Naviguez sans maximiser la fenêtre, sans installer d'extension, et sans vous connecter à vos vrais comptes.
|
||||
torproject.org →🔴
|
||||
Limites, pas magique
|
||||
Tor protège le transport, pas vos habitudes : si vous vous connectez à votre vrai compte, vous vous déanonymisez vous-même. Il est plus lent, et le nœud de sortie voit le trafic non chiffré (utilisez HTTPS). Pour un anonymat sérieux, associez-le à Tails (section 12).
|
||||
PARTIE 12 · 🔴
|
||||
Systèmes d'exploitation libres
|
||||
La pièce maîtresse. Si le scan côté client peut être imposé par le système d'exploitation lui-même, la seule vraie parade est de contrôler cet OS. C'est vrai sur le téléphone (GrapheneOS) comme sur l'ordinateur (Linux). Ne sous-estimez pas ce point : tant que vous restez sous Windows, macOS ou un Android Google, vous ne contrôlez pas vraiment votre machine.
|
||||
Gr
|
||||
GrapheneOS
|
||||
🔴 AvancéMobile dégoogliséAndroid · Pixel · open-source
|
||||
Un Android entièrement dégooglisé, installé sur un téléphone Pixel : la souveraineté sur mobile.
|
||||
C'est la défense structurelle par excellence contre un OS ou un magasin d'applications qui voudrait vous scanner. Services Google en bac à sable optionnel, permissions réseau par application, profils cloisonnés, redémarrage automatique qui purge les clés de la mémoire, code PIN de contrainte qui efface le téléphone. Réputé pour sa sécurité au point d'être utilisé par des cibles de logiciels espions.
|
||||
Profils exposés, mais aussi tout citoyen prêt à acheter un Pixel pour reprendre le contrôle de son téléphone.
|
||||
Procurez-vous un téléphone Google Pixel compatible (c'est le seul matériel supporté, pour des raisons de sécurité).
|
||||
Sur un ordinateur, allez sur grapheneos.org/install et suivez l'installeur web (quelques clics, aucun terminal requis).
|
||||
Pour vos applications, installez le magasin libre F-Droid ; ajoutez Google Play en bac à sable seulement si nécessaire.
|
||||
Réglez un long mot de passe (pas un code à 4 chiffres) et découvrez le mode « Lockdown ».
|
||||
grapheneos.org →🔴
|
||||
🐧
|
||||
Linux (sur ordinateur)
|
||||
🟡🔴Remplace Windows / macOS🌍 · libre & gratuit
|
||||
Un système d'exploitation libre et gratuit pour votre ordinateur, en remplacement de Windows ou de macOS. Il en existe de nombreuses variantes, appelées « distributions ».
|
||||
C'est le point aveugle de beaucoup de gens : ils chiffrent leurs messages mais gardent un Windows qui transmet en continu de la télémétrie à Microsoft (et dont la fonction « Recall » a voulu photographier l'écran en permanence), ou un macOS qui signale les applications que vous lancez. Linux, lui, ne vous espionne pas, il est gratuit, il ressuscite les vieux ordinateurs et il vous rend enfin maître de votre machine. C'est plus facile que sa réputation : les distributions modernes ressemblent à Windows ou macOS.
|
||||
Tout le monde peut franchir le pas. Pour débuter, Linux Mint (le plus proche de Windows) ou Fedora ; Debian pour la stabilité. Pas besoin de tout casser : on peut d'abord tester, puis installer à côté de son système actuel.
|
||||
Pas sûr de la distribution ? Répondez au questionnaire distrochooser.de/fr, qui vous oriente selon vos besoins.
|
||||
Testez-la sans rien installer directement dans votre navigateur sur distrosea.com.
|
||||
Une fois convaincu, téléchargez l'image de la distribution et créez une clé USB de démarrage (avec Ventoy ou Balena Etcher).
|
||||
Démarrez sur la clé pour essayer le système « en live », puis lancez l'installation. Activez le chiffrement du disque proposé.
|
||||
distrochooser.de/fr →distrosea.com →linuxmint.com →🟡🔴
|
||||
Ta
|
||||
Tails
|
||||
🔴 AvancéClé USB amnésique🌍 · tout via Tor
|
||||
Un système qui démarre depuis une clé USB, fait passer tout le trafic par Tor et ne laisse aucune trace à l'extinction.
|
||||
À l'arrêt, tout est oublié : c'est « amnésique ». Idéal pour un travail sensible et ponctuel sur un ordinateur qui n'est pas le vôtre, sans rien y laisser.
|
||||
Lanceurs d'alerte, journalistes, sources. L'outil de référence pour les missions à haut risque.
|
||||
Téléchargez Tails sur tails.net et suivez l'assistant pour créer la clé USB.
|
||||
Redémarrez l'ordinateur sur cette clé. Utilisez-le, puis éteignez : tout disparaît.
|
||||
tails.net →🔴
|
||||
Qb
|
||||
Qubes OS · postmarketOS
|
||||
🔴 ExpertCloisonnement🌍 · open-source
|
||||
Deux systèmes de niche : Qubes cloisonne votre PC en machines virtuelles étanches ; postmarketOS fait tourner du Linux sur d'anciens téléphones.
|
||||
Qubes isole chaque activité (travail, banque, navigation risquée) dans son propre compartiment : une compromission n'atteint pas le reste. postmarketOS prolonge la vie de téléphones abandonnés par leur constructeur.
|
||||
Utilisateurs experts avec un modèle de menace très élevé.
|
||||
qubes-os.org →postmarketos.org →🔴
|
||||
Durcir l'appareil
|
||||
Sans changer d'OS, on gagne déjà beaucoup : installer les applications via F-Droid (magasin libre) ou Aurora Store, remplacer les services Google par microG, créer des profils séparés, couper les permissions réseau inutiles, désactiver la sauvegarde cloud automatique et l'identifiant publicitaire.
|
||||
MATÉRIEL · 🔴
|
||||
Téléphonie & appareil physique
|
||||
La meilleure application ne sert à rien si l'appareil lui-même vous trahit. Voici les menaces matérielles et les gestes qui les neutralisent.
|
||||
Le numéro de téléphone est un traceur
|
||||
Votre numéro relie entre eux votre carte SIM, votre appareil (son identifiant IMEI), votre localisation et votre identité (la plupart des pays exigent une pièce d'identité à l'achat d'une SIM). C'est le fil qui permet de tout recouper.
|
||||
Utilisez des messageries sans numéro (SimpleX, Session) pour vos contacts sensibles, et préférez toujours Signal au SMS.
|
||||
Pour une identité séparée, une carte SIM « data uniquement » ou une eSIM prépayée limite le lien au point de vente (à vérifier selon les pays).
|
||||
IMSI-catchers & réseau téléphonique
|
||||
Les « IMSI-catchers » (ou « stingrays ») sont de fausses antennes-relais qui forcent les téléphones à s'y connecter pour enregistrer leur identifiant et parfois intercepter les communications, souvent en rétrogradant vers la 2G, peu chiffrée. En parallèle, une faille ancienne du réseau (SS7) permet encore d'intercepter des SMS et de localiser un téléphone à distance : une raison de plus d'abandonner le SMS.
|
||||
Désactivez la 2G dans les réglages (possible sur Android et GrapheneOS) pour couper le principal vecteur d'interception.
|
||||
En manifestation ou en zone sensible, passez en mode avion ou glissez le téléphone dans un sac de Faraday (qui bloque tout signal). L'EFF publie même un outil libre, Rayhunter, pour détecter les IMSI-catchers.
|
||||
Biométrie ou code ?
|
||||
Un doigt se force, pas un code
|
||||
On peut appliquer votre visage ou votre doigt sur le capteur pendant qu'on vous immobilise (à une frontière, lors d'une interpellation) ; un mot de passe dans votre tête, non. Avant une situation à risque, forcez le retour au code : sur iPhone, maintenez le bouton latéral et un bouton de volume (écran « SOS ») ; sur Android/GrapheneOS, utilisez le mode Lockdown du menu d'alimentation, qui désactive la biométrie jusqu'à saisie du code. Choisissez un mot de passe long, pas un code à quatre chiffres. GrapheneOS propose même un code de contrainte qui efface le téléphone.
|
||||
Dé-Microsoft, dé-Apple
|
||||
N'oubliez pas l'ordinateur : Windows transmet en continu de la télémétrie (et sa fonction « Recall » a voulu photographier l'écran en permanence), macOS signale les applications que vous lancez. La vraie sortie, c'est Linux (voir section 12). À défaut, désactivez la télémétrie, l'identifiant publicitaire et « Recall », et ajoutez un pare-feu sortant (comme Little Snitch sur Mac).
|
||||
PARTIE 13 · 🔴
|
||||
Anonymat & OPSEC
|
||||
Pour le compte pseudonyme et le lanceur d'alerte. Ici, on ne protège plus seulement un message : on protège une identité.
|
||||
Pseudonymat contre anonymat
|
||||
La règle d'or : ne jamais croiser votre identité réelle et votre identité publique. Pas le même e-mail, pas le même numéro, pas le même appareil, pas le même réseau, pas les mêmes horaires, pas le même style d'écriture. Une seule fuite suffit à relier les deux.
|
||||
Comptes sans identité : SimpleX et Session ne demandent aucun numéro ; associez-les à des alias e-mail jetables. Méfiez-vous des « numéros virtuels » revendus et traçables.
|
||||
Nettoyer les métadonnées : une photo publiée contient souvent la date, le modèle d'appareil et parfois les coordonnées GPS (données EXIF). Nettoyez avec Metadata Cleaner, mat2 ou ExifTool avant de publier ; attention aussi aux noms de fichiers et aux métadonnées de documents.
|
||||
Anti-corrélation réseau : utilisez Tor/Tails pour dissocier votre activité de votre IP domestique. Ne mélangez jamais réseau personnel et réseau pseudonyme. Une carte SIM à votre nom trahit votre localisation, peu importe le reste.
|
||||
Cloisonnement : un appareil ou au moins un profil dédié à l'identité publique, un gestionnaire de mots de passe séparé, jamais de connexion croisée entre les deux mondes.
|
||||
Transmettre des documents en tant que source
|
||||
SecureDrop est le système de dépôt anonyme utilisé par de nombreuses rédactions pour recevoir des documents de sources. Beaucoup de journaux publient aussi une clé PGP et un contact Signal. Ne transmettez jamais depuis votre matériel professionnel ni votre réseau habituel.
|
||||
Honnêteté, ne vous surestimez pas
|
||||
L'anonymat fort contre un adversaire étatique est difficile et faillible. Ce guide donne des repères, pas des garanties. Si des vies en dépendent, formez-vous auprès des références d'autorité : EFF Surveillance Self-Defense, Freedom of the Press Foundation, Privacy Guides. Cadre strictement défensif et journalistique.
|
||||
PARTIE 14 · POUR ALLER PLUS LOIN
|
||||
L'écosystème libre complet
|
||||
Se dégoogliser à fond, c'est remplacer chaque brique par un équivalent libre. La logique commune (logiciel libre + contrôle de son appareil + auto-hébergement) est la vraie parade au scan côté client.
|
||||
Bureautique
|
||||
LibreOffice
|
||||
OnlyOffice
|
||||
Média
|
||||
VLC
|
||||
Jellyfin
|
||||
FreeTube / NewPipe
|
||||
Audacity · OBS
|
||||
GIMP · Inkscape
|
||||
Calibre · Shotcut
|
||||
Cloud & réseau
|
||||
Nextcloud
|
||||
Pi-hole
|
||||
KDE Connect
|
||||
Mobile libre
|
||||
F-Droid · Aurora
|
||||
microG
|
||||
postmarketOS
|
||||
Sécurité
|
||||
Bitwarden
|
||||
KeePassXC
|
||||
Wireshark
|
||||
E-mail & bureau
|
||||
Thunderbird
|
||||
FairEmail
|
||||
Dev & avancé
|
||||
Termux · git
|
||||
QEMU / KVM · Wine
|
||||
Flatpak · GNU/Linux
|
||||
PARTIE 15 · PASSER À L'ACTION
|
||||
Migration & désobéissance civile numérique
|
||||
Un plan progressif
|
||||
Semaine 1 🟢 : Installez Signal et faites-y venir vos proches. Passez à Proton Mail ou Tuta. Installez un gestionnaire de mots de passe et uBlock Origin.
|
||||
Semaine 2 🟡 : Migrez vos fichiers vers un cloud chiffré, remplacez Chrome/Google Search, prenez un VPN, créez un compte Mastodon.
|
||||
Ensuite 🔴 : Selon votre profil : GrapheneOS, Tor/Tails, auto-hébergement, discipline OPSEC.
|
||||
Erreurs classiques
|
||||
Croire qu'un seul outil suffit ; réutiliser son vrai numéro pour un compte anonyme ; installer dix applications sans changer ses habitudes ; oublier que le maillon faible, c'est souvent l'appareil lui-même.
|
||||
La souveraineté est collective
|
||||
Une messagerie chiffrée ne sert que si votre interlocuteur l'utilise aussi. Faites migrer vos contacts, votre famille, votre communauté. Changer d'outils, c'est voter avec ses usages : chaque personne qui quitte les plateformes de surveillance affaiblit le modèle.
|
||||
Agir politiquement
|
||||
La technique ne remplace pas la politique. Suivez et soutenez la mobilisation : fightchatcontrol.eu, EDRi, le travail de Patrick Breyer. Contactez vos eurodéputés, c'est encore ce qui a fait vaciller le texte.
|
||||
Manifeste
|
||||
Refuser une surveillance de masse illégitime n'est pas se cacher : c'est un acte citoyen, légal et pacifique. La vie privée est un droit, pas un aveu. Chiffrer, c'est voter. S'auto-héberger, c'est désobéir. Reprendre ses outils, c'est devenir ingouvernable.
|
||||
Ressources
|
||||
Privacy Guides EFF · Surveillance Self-Defense Freedom of the Press Foundation Fight Chat Control
|
||||
Sources · le texte
|
||||
Chat Control · Wikipedia Breyer · vote 9 juillet 2026 Chat Control 1.0 vs 2.0 Proton · client-side scanning Appel commun des géants (19 mars 2026) Big Tech vows to keep scanning
|
||||
Agir
|
||||
European Digital Rights (EDRi) Patrick Breyer · Chat Control Signal · blog
|
||||
Code source ouvert sur GitHub
|
||||
Ce guide est un document d'information sur la protection de la vie privée et la défense du chiffrement. Il ne conseille aucune activité illégale. Vérifiez toujours l'actualité législative : la situation évolue vite. Dernière mise à jour des faits : 9 juillet 2026.
|
||||
@@ -1,833 +0,0 @@
|
||||
◆ EXIT CHAT CONTROL
|
||||
🇫🇷 FR
|
||||
🇳🇱 NL
|
||||
🇬🇧 EN
|
||||
◐
|
||||
Massasurveillance · EU · 2026
|
||||
Een veldgids voor digitale soevereiniteit
|
||||
Exit Chat Control_
|
||||
Chat Control wil uw privégescans lezen. Hier leest u hoe u stap voor stap weer controle krijgt over uw gesprekken, uw gegevens en uw hulpmiddelen, van beginner tot klokkenluider.
|
||||
🟢 Burger 🟡 Pseudoniem account 🔴 Klokkenluider
|
||||
Laatste nieuws: 9 juli 2026
|
||||
Het Europees Parlement heeft Chat Control 1.0 doorgelaten. De motie van afwijzing strandde: 314 Europarlementariërs tegen de tekst, 276 voor, 17 onthoudingen, maar er waren 361 stemmen nodig om hem te blokkeren. Het "vrijwillige" scannen van berichten is toegestaan tot 3 april 2028. Intussen is de 5e trialoog over Chat Control 2.0 (CSAR) op 29 juni mislukt; de onderhandelingen gaan door. De dreiging is niet geweken, ze nestelt zich.
|
||||
Inhoudsopgave
|
||||
00Begrijp de dreiging
|
||||
★Verwijderen / Overstappen
|
||||
01Versleutelde berichten
|
||||
02Versleutelde e-mail & PGP
|
||||
03Browser & zoeken
|
||||
◦Versleutelde DNS & Cloudflare
|
||||
04VPN: de waarheid
|
||||
✋Censuur & identiteit
|
||||
05Google verlaten (Proton)
|
||||
06Versleutelde opslag
|
||||
07Wachtwoorden
|
||||
◦2FA & hardwaretokens
|
||||
08Gedecentraliseerde netwerken
|
||||
09Financiële soevereiniteit
|
||||
⚠Conversationele AI
|
||||
◦Dagelijkse gereedschapskist
|
||||
10Zelfhosting
|
||||
11Tor
|
||||
12Vrije besturingssystemen & Linux
|
||||
◦Telefonie & apparaat
|
||||
13Anonimiteit & OPSEC
|
||||
14Het vrije-software-ecosysteem
|
||||
15In actie komen
|
||||
DEEL 00 · HET WAAROM
|
||||
Begrijp de dreiging
|
||||
Voordat u van hulpmiddelen wisselt, moet u begrijp waartegen u zich beschermt. Anders installeert u lukraak apps en denkt u veilig te zijn terwijl dat niet zo is.
|
||||
Chat Control 1.0 en 2.0, in klare taal
|
||||
"Chat Control" is de bijnaam voor twee Europese teksten die het scannen van uw privégescans toestaan (of zouden verplichten) op zoek naar kinderporno (CSAM). Het opgegeven doel is legitiem; het gebruikte middel, het blind scannen van de communicatie van niet-verdachte personen, komt neer op massasurveillance.
|
||||
Chat Control 1.0
|
||||
Chat Control 2.0 (CSAR)
|
||||
Tekst
|
||||
Verordening (EU) 2021/1232, ePrivacy-uitzondering
|
||||
Voorstel COM/2022/209 (Ylva Johansson, mei 2022)
|
||||
Aard van de scan
|
||||
Vrijwillig, platforms mogen scannen
|
||||
Verplicht via "detectiebevelen" en "risicobeperkende maatregelen"
|
||||
Doelwit
|
||||
Vooral niet-versleutelde Amerikaanse diensten: Gmail, Messenger/Instagram, Skype, Snapchat, iCloud Mail, Xbox
|
||||
Alle platforms, ook end-to-end versleutelde berichtendiensten
|
||||
Looptijd
|
||||
Tijdelijk, verlengd tot 3 april 2028
|
||||
Permanent
|
||||
Status (juli 2026)
|
||||
Aangenomen op 9 juli 2026
|
||||
In onderhandeling, 5e trialoog mislukt op 29 juni
|
||||
Client-side scannen: de wettelijke tap
|
||||
De kern van Chat Control 2.0 is een techniek die client-side scanning heet. Het idee: in plaats van de versleuteling tijdens het transport te breken, wordt software direct op uw telefoon geïnstalleerd die elk bericht, elke foto of elke link inspecteert voor het wordt versleuteld en verstuurd. Signal vat het in één zin samen: het is "zoals malware op uw apparaat".
|
||||
Technische eerlijkheid, lees twee keer
|
||||
Tegen client-side scannen zijn noch een VPN noch end-to-end-encryptie voldoende als de app of het besturingssysteem meewerkt. De scan vindt op het apparaat plaats, vóór de versleuteling: de VPN heeft niets te beschermen, en de versleuteling komt te laat. Wat u echt beschermt, is kiezen voor vrije software die weigert scanning in te bouwen, gunstige jurisdicties, zelfhosting, en de controle over uw apparaat terugwinnen (een ongeknoeid besturingssysteem). Al het andere in deze gids volgt uit deze waarheid.
|
||||
Het ergste: het werkt zelfs niet
|
||||
Ieders privacy wordt opgeofferd voor een technologie die faalt. De eigen studie van het Europees Parlement concludeert dat geen enkel systeem deze inhoud kan detecteren zonder hoge foutpercentages (omdat over miljarden berichten zelfs een klein percentage foutpositieven miljoenen valse beschuldigingen oplevert). De Ierse politiecijfers illustreren het: van de automatische meldingen was slechts zo'n 20% echt materiaal, en ruim 11% was overduidelijk foutpositief. Baanbrekend onderzoek ("Bugs in Our Pockets", 2021) toont bovendien aan dat client-side scanning, eenmaal geïnstalleerd, onvermijdelijk voor andere doelen wordt ingezet (terrorisme, auteursrecht, meningen).
|
||||
Het voorwendsel: "kinderen beschermen"
|
||||
Niemand is tegen de bescherming van minderjarigen, en dat is precies wat het zo'n effectieve hefboom maakt. De strijd tegen kindermisbruik dient als emotioneel Trojaans paard: wie zou het durven tegenspreken? Onder die vlag wordt een principe aanvaardbaar gemaakt dat op zichzelf resoluut verworpen zou worden: de geautomatiseerde inspectie van de privécommunicatie van honderden miljoenen niet-verdachte mensen. Kinderen zijn het opgegeven motief; het echte doelwit is ieders versleuteling en privacy.
|
||||
Het meest veelzeggend: de bedrijven die het hardst voor deze scan duwen, zijn de bedrijven wier verdienmodel surveillance is. Op 19 maart 2026 riep een gezamenlijke oproep EU-wetgevers op om "vrijwillige" detectie te verankeren, ondertekend door:
|
||||
GoogleLinkedInSnapchatMicrosoftTikTokMeta
|
||||
"Niets doen zou onverantwoord zijn." Het argument van de techreuzen om het scannen van berichten in stand te houden.
|
||||
Dezelfde spelers hebben overigens aangekondigd berichten te willen blijven scannen zelfs nadat de wettelijke grondslag op 4 april 2026 verviel. Aan burgers wordt dus gevraagd om de inspectie van hun intieme gesprekken toe te vertrouwen aan precies de bedrijven die erom bekend staan hun gegevens te verzamelen en te gelde te maken. Dit is het echte gezicht van Chat Control: een aanval op de privacy verpakt in een motief dat niemand kan weigeren.
|
||||
Dit is niet "gewoon" surveillance
|
||||
Men probeert u gerust te stellen: "het gaat alleen om Gmail, Instagram, Snapchat, Messenger." Dat is onwaar, en het is juist het meest alarmerende. Drie verschuivingen schuilen achter die bagatellisering.
|
||||
Dit is geen gerichte aanpak, dit is een sleepnet. Hier worden geen verdachten gevolgd: er wordt een systematische verzameling van de communicatie van hele bevolkingsgroepen geïnstalleerd. Surveillance op staatsschaal, voortdurend.
|
||||
Het is niet langer lezen, het is de macht om te blokkeren. Software die een bericht vóór het versturen inspecteert, kan het ook weigeren te versturen. Client-side scanning bouwt de infrastructuur van voorafgaande censuur: spreek blokkeren voordat het zelfs maar bij de ontvanger bestaat.
|
||||
Is de infrastructuur er eenmaal, dan wordt ze voor iets anders gebruikt. Een systeem dat "voor de kinderen" is gebouwd, wordt een universeel controlemiddel. Het motief verandert; de machine blijft.
|
||||
De logische volgende stap: de digitale euro
|
||||
Dezelfde logiek staat op het punt ook uw geld te bereiken. Het digitale-europlan van de ECB voorziet in een bezitplafond van ongeveer €3.000 per persoon, verbiedt bedrijven om er een aan te houden, en rust op een traceerbare munteenheid. De ECB zwoer dat het niet "programmeerbaar" zou zijn, maar het plafond en de traceerbaarheid staan duidelijk op het menu, en het kader werd in juni 2026 in commissie goedgekeurd door het Europees Parlement. Wanneer het zover is, zult u dan zeggen "kalm, het is maar tot €3.000"? Dat is precies dezelfde retorische valstrik als "het is maar Gmail". De echte vraag is nooit wat vandaag wordt gecontroleerd, maar welke controle-infrastructuur voor morgen wordt gebouwd.
|
||||
Welk profiel bent u?
|
||||
Geen enkel hulpmiddel is magisch en niemand hoeft alles te doen. Bepaal uw profiel: elke sectie geeft aan hoe ver u moet gaan.
|
||||
Beginner
|
||||
🟢 De gewone burger
|
||||
U wilt simpelweg dat uw gesprekken en uw privéleven niet langer worden gescand en te gelde gemaakt. Doel: dagelijkse privacy, zonder expert te worden.
|
||||
Gevorderd
|
||||
🟡 Het pseudoniem-account
|
||||
Informatieaccount, activistenpagina, maker: u vreest gedéanonimiseerd, gedoxxt of uw account te verliezen. Doel: uw echte identiteit gescheiden houden van uw publieke identiteit.
|
||||
Expert
|
||||
🔴 De klokkenluider
|
||||
Journalist, activist, bron, tegenover een machtige tegenstander (staat, werkgever). Doel: sterke anonimiteit, anti-correlatie, documenten doorspelen zonder gepakt te worden.
|
||||
MEMO · DE DIGITALE OMMEZWAAI
|
||||
Verwijderen / Overstappen
|
||||
Het grote geheel in één oogopslag. Elke tool wordt in de genummerde secties hieronder gedetailleerd.
|
||||
Verwijderen
|
||||
WhatsApp (Meta)
|
||||
Messenger (Meta)
|
||||
Instagram (Meta)
|
||||
Snapchat
|
||||
Skype (Microsoft)
|
||||
Discord
|
||||
Gmail (Google)
|
||||
Outlook (Microsoft)
|
||||
Google Drive / Photos
|
||||
OneDrive (Microsoft)
|
||||
TikTok
|
||||
Telegram (normale chats)
|
||||
Overstappen
|
||||
Signal
|
||||
SimpleX Chat
|
||||
Session
|
||||
Bitchat
|
||||
Element (Matrix)
|
||||
Nostr
|
||||
Mastodon
|
||||
Proton Mail
|
||||
Tuta Mail
|
||||
Mullvad (VPN & browser)
|
||||
GrapheneOS
|
||||
DEEL 01 · DE FUNDAMENTEN · 🟢
|
||||
Versleutelde berichten
|
||||
De eerste stap, degene die u het snelst beschermt: weg van WhatsApp, Messenger en Telegram, naar een echt versleutelde (en idealiter vrije) berichtendienst.
|
||||
U stapt weg van
|
||||
U stapt over op
|
||||
Waarom
|
||||
WhatsApp
|
||||
Signal
|
||||
Even eenvoudig, zonder Meta of metadata-verzameling
|
||||
Messenger
|
||||
Signal / SimpleX
|
||||
Messenger is niet standaard end-to-end versleuteld en werd gescand onder Chat Control 1.0
|
||||
Telegram
|
||||
Signal / SimpleX
|
||||
Telegram is niet standaard end-to-end versleuteld
|
||||
S
|
||||
Signal
|
||||
🟢 BeginnerVervangt WhatsApp🇺🇸 · open-source
|
||||
Een versleutelde berichtendienst voor uw sms'jes, oproepen, video's en groepen. De directe vervanger van WhatsApp, even eenvoudig.
|
||||
Standaard end-to-end-encryptie, non-profit, een geauditeerd protocol dat de wereldwijde standaard is. Vrijwel geen metadata bewaard, en het team heeft beloofd de EU te verlaten liever dan scanning in te bouwen.
|
||||
Iedereen, vandaag nog. Het is de eerste en nuttigste stap. Eén voorwaarde: een telefoonnummer, dat u kunt verbergen achter een gebruikersnaam.
|
||||
Installeer vanaf signal.org (App Store, Google Play, of de directe APK op Android).
|
||||
Bevestig uw nummer via sms, en kies dan een pincode.
|
||||
Instellingen > Privacy: maak een gebruikersnaam aan om uw nummer te verbergen, en schakel het registratieslot in.
|
||||
Nodig uw naasten uit. Koppel op de computer de desktop-app aan uw telefoon.
|
||||
signal.org →🟢🟡🔴
|
||||
SX
|
||||
SimpleX Chat
|
||||
🟡 GevorderdZonder identificatie🌍 · open-source
|
||||
Een versleutelde berichtendienst zonder account, nummer of identificatie. U maakt verbinding door een link of QR-code te delen.
|
||||
Per ontwerp weten zelfs de servers niet wie met wie praat. Het is op dit moment de sterkste optie tegen metadata-correlatie.
|
||||
Pseudoniem-accounts, gevoelige contacten, hoog risico. Iets jonger dan Signal, met wat ruwe kantjes.
|
||||
Installeer vanaf simplex.chat (F-Droid, Google Play, App Store of APK).
|
||||
Kies een lokale weergavenaam (nooit uw echte naam: die blijft op uw apparaat).
|
||||
Deel om een contact toe te voegen een eenmalige uitnodigingslink of QR-code.
|
||||
Schakel de toegangscode in, en (voor gevorderden) configureer uw eigen SMP-servers.
|
||||
simplex.chat →🟡🔴
|
||||
Se
|
||||
Session
|
||||
🟡 GevorderdZonder nummer🌍 · open-source
|
||||
Versleuteling op Signal-niveau, maar zonder telefoonnummer en met onion-routing.
|
||||
Uw identiteit is een willekeurige "Session ID". Het verkeer gaat via een gedecentraliseerd netwerk (Lokinet) dat metadata sterk vermindert.
|
||||
Wanneer een telefoonnummer een risico is. Kleiner ecosysteem, het beste voor gesprekken die ertoe doen.
|
||||
Installeer vanaf getsession.org (alle platforms).
|
||||
"Create account" genereert uw Session ID en een herstelzin.
|
||||
Noteer de herstelzin op papier: het is de enige sleutel tot uw account.
|
||||
Deel uw Session ID (of QR) met uw contacten.
|
||||
getsession.org →🟡🔴
|
||||
Br
|
||||
Briar
|
||||
🔴 ExpertOffline / P2PAndroid · open-source
|
||||
Een peer-to-peer berichtendienst die werkt zonder internet: via Bluetooth, Wi-Fi Direct of Tor.
|
||||
Geen server, dus niets om af te sluiten of in beslag te nemen. Bestand tegen internetstoringen en netwerkcensuur.
|
||||
Betogingen, rampen, afgelegen gebieden, black-outs. Alleen Android, en beide personen moeten de app hebben.
|
||||
Installeer vanaf briarproject.org of F-Droid (geen iOS).
|
||||
Maak een lokaal account aan (bijnaam + wachtwoord), alleen op de telefoon opgeslagen.
|
||||
Voeg een nabij contact toe door de QR-code te scannen, of op afstand via een link.
|
||||
Wanneer het netwerk uitligt, zet Bluetooth aan: berichten springen van apparaat naar apparaat.
|
||||
briarproject.org →🔴
|
||||
bit
|
||||
Bitchat
|
||||
🔴 ExpertBluetooth-mesh🌍 · open-source
|
||||
De mesh-berichtendienst van Jack Dorsey: nabijgelegen telefoons geven berichten van apparaat naar apparaat door, zonder internet, server of account.
|
||||
End-to-end-encryptie (AES-256-GCM) en een "paniekmodus" die alles wist met drie tikken op het logo.
|
||||
Betogingen en black-outs, als aanvulling op Briar. Met voorzichtigheid te gebruiken.
|
||||
Voorzichtig: niet geauditeerd
|
||||
De eigen repository waarschuwt dat het "geen externe beveiligingsreview heeft ontvangen". Geweldig voor veerkracht, maar zet er voorlopig geen levens op het spel.
|
||||
Installeer via de App Store (iOS) of de APK/GitHub (Android).
|
||||
Open de app en kies een bijnaam. Geen account aan te maken.
|
||||
Nabijgelegen apparaten vinden elkaar automatisch via Bluetooth.
|
||||
Tik in gevaar driemaal op het logo om alles te wissen (paniekmodus).
|
||||
bitchat →🔴
|
||||
M
|
||||
Molly
|
||||
🔴 ExpertGeharde SignalAndroid · open-source
|
||||
Een geharde versie van Signal voor Android, compatibel met hetzelfde netwerk.
|
||||
Database in rust versleuteld, automatische vergrendeling, en een "FOSS"-variant volledig zonder Google-diensten.
|
||||
Gevorderde gebruikers, vooral op GrapheneOS, met een hoog dreigingsmodel. Alleen Android.
|
||||
Voeg de Molly-repo toe in F-Droid, of haal de APK van molly.im.
|
||||
Kies Molly-FOSS als u geen Google-afhankelijkheden wilt.
|
||||
Registreer met uw nummer (hetzelfde netwerk als Signal).
|
||||
Stel een databasewachtwoord en automatische vergrendeling in.
|
||||
molly.im →🔴
|
||||
Tegen Chat Control
|
||||
Geef de voorkeur aan vrije software buiten de grote Amerikaanse platforms, waarvan de teams publiekelijk hebben beloofd de EU te verlaten liever dan client-side scanning in te bouwen (Signal heeft dat gedaan). Herinnering: als het besturingssysteem zelf scanning afdwingt, kan de app niet helpen, vandaar sectie 12.
|
||||
DEEL 02 · 🟢
|
||||
Versleutelde e-mail & PGP
|
||||
Gmail leest uw e-mails en is gevestigd in de VS, onder de jurisdictie van de "Five Eyes". Gmail/Outlook verlaten is een enorme winst voor minimale inspanning.
|
||||
Pm
|
||||
Proton Mail
|
||||
🟢 BeginnerVervangt Gmail🇨🇭 · open-source · OpenPGP
|
||||
Een versleutelde inbox die Gmail of Outlook vervangt, en het spilaccount voor het hele Proton-ecosysteem.
|
||||
Gevestigd in Zwitserland, buiten de "Five Eyes"-jurisdictie. OpenPGP-compatibel: automatische versleuteling tussen Proton-accounts en naar andere PGP-diensten.
|
||||
Iedereen. Het is het beste startpunt om Google te verlaten (zie sectie 05 voor de rest van de suite).
|
||||
Maak een account aan op proton.me (het gratis abonnement is voldoende om te starten).
|
||||
Importeer uw oude berichten en contacten met Easy Switch (ingebouwd).
|
||||
Stel een doorzending in vanuit Gmail en breng uw contacten op de hoogte van uw nieuwe adres.
|
||||
Installeer de mobiele app, schakel tweestapsverificatie in, en gebruik Proton Bridge als u Thunderbird wilt blijven gebruiken.
|
||||
proton.me/mail →🟢🟡
|
||||
Tu
|
||||
Tuta
|
||||
🟢 BeginnerVervangt Gmail🇩🇪 · open-source · post-quantum
|
||||
Een Duitse e-mailservice, volledig open-source en vrij van elke Google-dienst.
|
||||
Het versleutelt ook het onderwerp en een deel van de metadata, met post-quantum-bestendige versleuteling. EU-jurisdictie.
|
||||
Wie maximale versleutelde metadata en 100% vrije software wil. Het gebruikt geen PGP: om versleuteld naar externen te schrijven, beveiligt u het bericht met een wachtwoord.
|
||||
Maak een account aan op tuta.com en installeer de app (beschikbaar op F-Droid).
|
||||
Om versleuteld te schrijven naar een niet-gebruiker: klik op het slot en stel een wachtwoord in, dat u via een ander kanaal aan uw correspondent doorgeeft.
|
||||
Schakel tweestapsverificatie in.
|
||||
tuta.com →🟢🟡
|
||||
mx
|
||||
mailbox.org · Posteo
|
||||
🟢 BeginnerEthische klassieke mail🇩🇪 · UE · PGP
|
||||
Twee ethische, goedkope Europese e-mailproviders, met PGP-ondersteuning aan serverzijde.
|
||||
Privacy- en milieubewust, gebouwd op open standaarden (IMAP, PGP). Ze werken met elke mailclient.
|
||||
Wie een "klassieke" mailbox (in Thunderbird) wil, maar dan ethisch, in plaats van een gesloten versleutelde app.
|
||||
Maak een account aan (betaald, ongeveer €1 tot €3 per maand).
|
||||
Schakel PGP-versleuteling aan serverzijde in (de "Guard"-functie bij mailbox.org).
|
||||
Configureer het account in Thunderbird of de mobiele app van uw keuze.
|
||||
mailbox.org →posteo.de →🟢
|
||||
PGP in één minuut
|
||||
PGP (Pretty Good Privacy) is versleuteling op basis van sleutels: u hebt een publieke sleutel die u uitdeelt, en een privésleutel die u geheim houdt. Iedereen met uw publieke sleutel kan u een bericht sturen dat alleen u kunt ontsleutelen. Het is de basis van provider-onafhankelijke versleutelde e-mail. Proton Mail regelt het automatisch; voor handmatig gebruik werkt OpenPGP via Thunderbird (ingebouwd sinds v78).
|
||||
DEEL 03 · 🟢
|
||||
Browser & zoeken
|
||||
Chrome is een reclametracker. Google Search profieleert elke zoekopdracht. Vervang beide.
|
||||
Mb
|
||||
Mullvad Browser
|
||||
🟡🔴 Gevorderd / ExpertAnti-vingerafdruk🇸🇪 · open-source
|
||||
De browser van de Tor Browser, maar dan zonder het Tor-netwerk: gebruik hem met uw VPN voor een moeilijk te volgen computer.
|
||||
Ontworpen met het Tor-project: alle gebruikers hebben dezelfde digitale vingerafdruk. Het resultaat: in plaats van uniek en dus traceerbaar te zijn, lost u op in de massa. De beste privék euze op de desktop.
|
||||
Wie verder wil gaan dan eenvoudig adblocken, vooral pseudoniem-accounts en blootgestelde profielen.
|
||||
Download hem van mullvad.net/browser (Windows, macOS, Linux).
|
||||
Gebruik hem standaard, zonder aanpassingen: elke toegevoegde extensie of instelling maakt u opnieuw identificeerbaar.
|
||||
Combineer hem met uw VPN om ook uw IP-adres te verbergen.
|
||||
mullvad.net/browser →🟡🔴
|
||||
Fx
|
||||
Firefox (gehard)
|
||||
🟢 BeginnerVervangt Chrome🌍 · open-source
|
||||
De referentie-vrije browser voor privacyvriendelijk dagelijks gebruik.
|
||||
In tegenstelling tot Chrome (een reclametracker van Google) wordt Firefox ontwikkeld door een stichting die er geen belang bij heeft u te profileren. Met enkele aanpassingen (de arkenfox-gids) en de extensie uBlock Origin wordt hij heel solide.
|
||||
Iedereen, als dagelijkse hoofd browser.
|
||||
Installeer Firefox vanaf mozilla.org/firefox.
|
||||
Voeg de extensie uBlock Origin toe (blokkeert advertenties en trackers).
|
||||
Kies bij privacy-instellingen "Streng", schakel telemetrie uit en wis cookies bij het afsluiten. Voor wie verder wil: pas de arkenfox-gids toe.
|
||||
mozilla.org/firefox →🟢
|
||||
Bv
|
||||
Brave
|
||||
🟢 BeginnerBlokkeert standaard alles🌍 · open-source
|
||||
Een op Chromium gebaseerde browser die standaard advertenties en trackers blokkeert, met een privévenster dat met Tor is verbonden.
|
||||
Het is de eenvoudigste optie voor wie van Chrome komt: dezelfde ergonomie, maar zonder tracking. Het blokkeert YouTube-advertenties standaard, biedt versleutelde video (Brave Talk, gebaseerd op Jitsi) en Tor-toegang met één klik, handig om een geblokkeerde site te bereiken.
|
||||
Beginners die een pijnloze overstap vanuit Chrome willen. (Negeer de crypto-/beloningsadvertentiefuncties als ze u niet interesseren.)
|
||||
Installeer Brave vanaf brave.com.
|
||||
Stel "Shields" in op "agressief", en schakel Brave Rewards uit als u geen advertenties wilt.
|
||||
Voor Tor: menu > "Nieuw privévenster met Tor", wacht tot "Tor verbonden" verschijnt.
|
||||
brave.com →🟢
|
||||
uB
|
||||
uBlock Origin
|
||||
🟢 BeginnerExtensie🌍 · open-source
|
||||
De browserextensie die advertenties, trackers en spionagescripts blokkeert. De actie met het hoogste rendement, in tien seconden.
|
||||
Online advertenties zijn de belangrijkste vector van surveillance en profilering. Het blokkeren ervan snijdt het leeuwendeel van de tracking weg, versnelt pagina's en verkleint het risico op malware.
|
||||
Iedereen, op Firefox en op Chromium-browsers.
|
||||
Open de extensiewinkel van uw browser en zoek "uBlock Origin" (het origineel, van Raymond Hill).
|
||||
Installeer hem. Hij werkt meteen, geen instellingen nodig.
|
||||
ublockorigin.com →🟢
|
||||
🔎
|
||||
DuckDuckGo · Brave Search · SearXNG
|
||||
🟢🟡 ZoekmachinesVervangt Google Search
|
||||
Zoekmachines die elke zoekopdracht niet profileren, in tegenstelling tot Google.
|
||||
DuckDuckGo en Brave Search bewaren geen geschiedenis die aan uw identiteit is gekoppeld. Startpage levert Google-resultaten zonder de tracking. SearXNG gaat verder: een zelf-hostbare metazoekmachine, soevereiniteit tot in uw zoekbalk.
|
||||
Iedereen. SearXNG is bedoeld voor wie zijn eigen engine wil hosten.
|
||||
Wijzig in uw browserinstellingen de standaardzoekmachine in DuckDuckGo, Brave Search of Startpage.
|
||||
Voor SearXNG: gebruik een publieke instance (gids op searx.space) of host uw eigen (zie sectie 10).
|
||||
duckduckgo.com →search.brave.com →searx.space →🟢🟡
|
||||
NETWERK · DNS · 🟡
|
||||
Versleutelde DNS & Cloudflare
|
||||
DNS is de telefoongids die een sitenaam (example.com) omzet in een machineadres. Het addertje: standaard beheert uw internetaanbieder die gids, dus ziet hij elke site die u bezoekt, zelfs wanneer de pagina HTTPS gebruikt. Uw DNS versleutelen haalt die lijst terug bij uw provider. Het is een stille instelling, maar een van de meest effectieve.
|
||||
9
|
||||
Quad9
|
||||
🟢 BeginnerAnti-malware🇨🇭 non-profit
|
||||
Een versleutelde, Zwitserse, non-profit DNS-resolver die ook bekende kwaadaardige domeinen blokkeert.
|
||||
Zwitserse jurisdictie, geen bewaring van uw IP-adres, bescherming tegen phishing en malware. Een uitstekende "instellen en vergeten"-keuze.
|
||||
Iedereen, vooral wie eenvoud en een goede jurisdictie wil.
|
||||
Op Android: Instellingen > Netwerk > Privé-DNS > voer dns.quad9.net in.
|
||||
Op iPhone/Mac: installeer het DoH-profiel van quad9.net. Op pc: stel DNS-over-HTTPS in de browser of het systeem in.
|
||||
quad9.net →🟢🟡
|
||||
DNS
|
||||
Mullvad DNS · NextDNS
|
||||
🟡 GevorderdNetwerkfiltering
|
||||
Twee versleutelde resolvers die ook advertenties en trackers blokkeren voor het hele apparaat.
|
||||
Mullvad DNS (Zweden, no-log) is gratis en biedt blokkeerlijsten. NextDNS is zeer aanpasbaar, met een dashboard en profielen per apparaat, maar is in de VS gevestigd en logt standaard (schakel dit uit in de instellingen).
|
||||
Wie systeembrede advertentieblokkering wil zonder per browser een extensie te installeren.
|
||||
Mullvad DNS: gebruik het adres op mullvad.net/help/dns als uw privé-DNS (dezelfde methode als Quad9).
|
||||
NextDNS: maak een profiel op nextdns.io, schakel logging uit en pas de configuratie toe op uw apparaten.
|
||||
mullvad.net/dns →nextdns.io →🟡
|
||||
CF
|
||||
Cloudflare 1.1.1.1 (met nuance)
|
||||
🟢 BeginnerSnel maar gecentraliseerd🇺🇸
|
||||
De versleutelde DNS 1.1.1.1 en de WARP-app van Cloudflare: snel, gratis, en een echte verbetering ten opzichte van de resolver van uw provider.
|
||||
Het is nuttig, met een geauditeerde belofte van geen logging. Maar wees reëel: Cloudflare staat al vóór een enorm deel van het web (als technische tussenpersoon kan het het platte tekstverkeer naar die sites zien). Ook uw DNS daarheen routeren betekent nog meer van uw sporen concentreren bij één Amerikaans bedrijf onder Amerikaans recht.
|
||||
Goed voor dagelijks gebruik en malware-blokkering (1.1.1.1 for Families). Vermijden of diversifiëren voor gevoelig gebruik: kies liever Quad9 of Mullvad DNS.
|
||||
1.1.1.1 →🟢
|
||||
π
|
||||
Pi-hole · AdGuard Home
|
||||
🔴 ExpertBlokkering voor het hele netwerk🌍 · open-source
|
||||
Een advertentie- en trackerblokkering die uw hele netwerk in één keer beschermt, geïnstalleerd op een kleine computer zoals een Raspberry Pi.
|
||||
Waar uBlock Origin één browser beschermt, filtert Pi-hole elk apparaat in huis, inclusief apparaten waarop u geen extensie kunt installeren: telefoons, smart-tv's, connected objects. Advertenties en trackers worden op DNS-niveau geblokkeerd, nog vóór ze laden. AdGuard Home is een alternatief met een modernere interface.
|
||||
Huishoudens die al hun apparaten in één keer willen opschonen, en die een (of willen) Raspberry Pi of kleine server hebben.
|
||||
Voer op een Raspberry Pi (of een container) het officiële installatieprogramma van Pi-hole uit.
|
||||
Stel in uw router-instellingen het adres van de Pi-hole in als DNS-server.
|
||||
Voortaan worden alle apparaten op uw netwerk automatisch gefilterd.
|
||||
pi-hole.net →adguard home →🔴
|
||||
DEEL 04 · 🟢
|
||||
VPN: de waarheid
|
||||
Een VPN is nuttig, maar wordt met veel leugens verkocht. Dit is wat het daadwerkelijk beschermt.
|
||||
Wat een VPN NIET doet
|
||||
Een VPN beschermt u niet tegen de client-side scanning van Chat Control: die leest uw berichten op uw apparaat, vóór de versleuteling en dus vóór de VPN. Het is ook geen onzichtbaarheidsmantel: uw besturingssysteem en apps zien meer dan uw VPN-provider.
|
||||
Wat een VPN wél goed doet
|
||||
Het verbergt uw activiteit voor uw internetaanbieder en maskeert uw IP-adres (en dus locatie) voor de sites die u bezoekt. Nuttig tegen netwerksurveillance, geolocatie en censuur. Eén stukje van de puzzel, niet de oplossing.
|
||||
Mv
|
||||
Mullvad VPN
|
||||
🟢🟡🔴 Alle niveausZonder account · cash/Monero🇸🇪 · open-source
|
||||
Een VPN die uw IP-adres maskeert en uw verkeer versleutelt tot aan zijn servers, zonder ooit ook maar één stukje persoonlijke informatie te vragen.
|
||||
Het is de gouden standaard voor privacy. Geen e-mail, geen naam: bij inschrijving krijgt u een eenvoudig accountnummer. Geauditeerd no-log-beleid, één eerlijke prijs (€5 per maand, geen niveaus, geen valse beloften), en betaling mogelijk in cash of Monero. Dat is precies wat ertoe doet op de dag dat een staat de VPN-toegang probeert af te snijden: een account dat niet aan uw identiteit te koppelen is, en een betaling die banken niet kunnen blokkeren.
|
||||
Iedereen, van de burger die een identiteitscontrole wil omzeilen tot het hoogrisicoprofiel. Dit is de VPN om als eerste aan te bevelen.
|
||||
Ga naar mullvad.net. Als de site in uw land geblokkeerd is, open hem dan via de Tor Browser of het .onion-adres. Klik op "Generate account number": er wordt niet om een e-mail of naam gevraagd. Bewaar dit nummer in uw wachtwoordmanager en deel het nooit, het is uw enige toegangssleutel.
|
||||
Kies de looptijd en de betaalmethode. De prijs staat vast. Drie wegen: bankkaart (het eenvoudigst indien niet geblokkeerd), per post verzonden contanten, of cryptocurrency.
|
||||
Anonieme cryptobetaling: klik op "Create a one-time payment address". Mullvad toont een adres (Bitcoin of Monero) en een exact bedrag. Stuur dat bedrag vanuit uw portemonnee. Voor maximale anonimiteit kiest u Monero (standaard privé); Bitcoin is eenvoudiger maar traceerbaar. Zie sectie 09 om crypto te kopen. Reken op circa 30 minuten bevestiging.
|
||||
Download de app van mullvad.net/download (via Tor als de site wordt gefilterd) en installeer hem. Op mobiel: App Store, Google Play, of de officiële APK als de app uit uw store is verwijderd.
|
||||
Start de app, voer uw accountnummer in en maak verbinding met een server buiten uw land, idealiter buiten de Europese Unie (Zwitserland, VS…). Schakel de "kill switch" in (die het internet afsluit als de VPN wegvalt) en de DNS van Mullvad.
|
||||
mullvad.net →🟢🟡🔴
|
||||
Pv
|
||||
Proton VPN
|
||||
🟢 BeginnerEerlijk gratis abonnement🇨🇭 · open-source
|
||||
De VPN uit het Proton-ecosysteem, met een echt gratis abonnement (zeldzaam en eerlijk).
|
||||
In Zwitserland gevestigd, geauditeerd, open-source. Het gratis abonnement heeft geen advertenties, geen doorverkoop van gegevens en geen datalimiet (alleen het aantal landen is beperkt). De ideale opstap voor een beginner, vooral als u al Proton Mail gebruikt.
|
||||
Beginners, en iedereen die al in het Proton-ecosysteem zit en één account voor alles wil.
|
||||
Ga naar protonvpn.com en meld u aan met uw Proton-account (of maak er een aan).
|
||||
Installeer de app (Windows, macOS, Linux, Android, iOS).
|
||||
Gebruik "Quick Connect" of kies een land, schakel de kill switch en NetShield in (blokkering van advertenties en trackers).
|
||||
protonvpn.com →🟢
|
||||
iv
|
||||
IVPN
|
||||
🟡🔴 Gevorderd / ExpertZonder e-mail · no-log🇬🇮 · open-source
|
||||
Een kleine, bijzonder rigoureuze en transparante VPN-provider, volgens hetzelfde model als Mullvad.
|
||||
Geauditeerd no-log-beleid, geen verplichte e-mail, betaling in crypto of contanten. Het biedt zelfs anti-trackingopties (blokkering van trackers op netwerkniveau) en inschrijving zonder enige identificatie.
|
||||
Een solide alternatief voor Mullvad, voor pseudoniem- en hoogrisicoprofielen die willen diversifiëren.
|
||||
Genereer op ivpn.net een account (er wordt een ID voor u aangemaakt, geen e-mail nodig).
|
||||
Betaal in crypto of contanten, net als bij Mullvad.
|
||||
Installeer de app, maak verbinding buiten de EU, schakel de kill switch en het WireGuard-protocol in.
|
||||
ivpn.net →🟡🔴
|
||||
CENSURE · DIGITALE IDENTITEIT · 🟡
|
||||
Censuur & identiteitscontroles
|
||||
Hetzelfde voorwendsel ("bescherm minderjarigen") wordt gebruikt om identiteitscontroles af te dwingen om toegang tot internet te krijgen. Het is het geplande einde van online anonimiteit, en dus een vorm van censuur. Hier is de stand van zaken, en hoe u er wettelijk aan kunt ontsnappen.
|
||||
Stand van zaken (2025-2026)
|
||||
Frankrijk. De SREN-wet (mei 2024) geeft toezichthouder Arcom de leiding over leeftijdsverificatie; sinds april 2025 moeten de betrokken sites dit invoeren. Een decreet dat leeftijdscontroles oplegt, werd op 15 juli 2025 bekrachtigd door de Raad van State, en een wetsvoorstel dat sociale media verbiedt voor -15-jarigen werd op 26 januari 2026 aangenomen in de Assemblée, wat betekent dat de leeftijd, en dus de identiteit, van iedereen moet worden geverifieerd.
|
||||
Verenigd Koninkrijk. Sinds 25 juli 2025 vereist de Online Safety Act "hoogst effectieve" leeftijdsverificatie (ID, creditcard, gezichtsschatting). Het directe resultaat: VPN-aanmeldingen schoten binnen enkele uren met meer dan 1.400% omhoog. Het House of Lords debatteerde begin 2026 zelfs over een minimumleeftijd voor VPN-gebruik zelf.
|
||||
Europese Unie. De Commissie presenteerde een leeftijdsverificatie-app (april 2025), gepiloteerd in vijf landen, ontworpen om aan te sluiten op de Europese digitale-identiteitsportemonnee (EUDI Wallet, eIDAS 2) die elke staat eind 2026 moet aanbieden. EFF en EDRi waarschuwen: het "zero-knowledge"-bewijs is slechts optioneel, en dit bouwt een online-ID-infrastructuur die veel verder gaat dan kinderbescherming.
|
||||
Er wettelijk aan ontsnappen
|
||||
De tegenzet komt neer op één idee: neem een IP-adres in een land dat deze controles niet oplegt, en verkrijg de hulpmiddelen anoniem, zelfs als ze op een dag worden geblokkeerd.
|
||||
Een VPN buiten de jurisdictie. Maak verbinding met een server buiten het filterende land (idealiter buiten de EU en de "14 Eyes"), met een geauditeerd no-log-beleid. Dit herstelt de toegang tot netwerken die om een identiteitsbewijs vragen.
|
||||
Anonieme betaling. Betaal de VPN in crypto of contanten (Mullvad, IVPN, Proton accepteren dit). Cruciaal: als VPN's ooit worden "verboden", kunnen betalingsverwerkers kaarten weigeren; cash en crypto zouden dan de enige opties zijn.
|
||||
Tor om een geblokkeerde VPN op te halen. Als de site van de provider vanuit uw land onbereikbaar wordt, gebruik dan de Tor Browser (of een .onion-adres) om de client te downloaden. Eenmaal geïnstalleerd is de VPN veel sneller voor dagelijks gebruik.
|
||||
Wijzig het land van uw account. Een Apple/Google-account dat op een ander land is ingesteld, laat apps die uit uw lokale store zijn verwijderd opnieuw verschijnen.
|
||||
De wet verandert, controleer
|
||||
Het omzeilen van een geografische beperking is in de meeste Europese landen legaal, maar het terrein verschuift snel: Utah (VS) heeft VPN-omzeiling strafbaar gesteld, en de Britse Lords kijken naar VPN-beperkingen. Controleer de wet van uw land voordat u op een methode vertrouwt. Deze gids gaat over privacybescherming, niet over het verbergen van een misdrijf.
|
||||
DEEL 05 · NEEM DE CONTROLE TERUG · 🟡
|
||||
Google verlaten
|
||||
Google is één account dat uw e-mail, agenda, bestanden, bewegingen en zoekopdrachten kent. Vervang de hele suite.
|
||||
Google
|
||||
Proton
|
||||
Overige
|
||||
Gmail
|
||||
Proton Mail
|
||||
Tuta, mailbox.org
|
||||
Google Drive
|
||||
Proton Drive
|
||||
Nextcloud, Cryptomator
|
||||
Google Calendar
|
||||
Proton Calendar
|
||||
Tuta Calendar
|
||||
Google Password
|
||||
Proton Pass
|
||||
Bitwarden, KeePassXC
|
||||
Google One VPN
|
||||
Proton VPN
|
||||
Mullvad, IVPN
|
||||
De aantrekkingskracht van Proton (Zwitserland, non-profit stichting, open-source): één account vervangt heel Google, met end-to-end-encryptie en een jurisdictie buiten de "Five Eyes". U kunt geleidelijk migreren, dienst per dienst. Wie liever niet opnieuw centraliseert bij één aanbieder, vindt in elke rij een onafhankelijk alternatief.
|
||||
DEEL 06 · 🟡
|
||||
Versleutelde opslag
|
||||
Pd
|
||||
Proton Drive
|
||||
🟢 BeginnerVervangt Google Drive / iCloud🇨🇭 · E2EE
|
||||
Een end-to-end versleutelde cloud voor uw bestanden en foto's, onderdeel van Proton.
|
||||
In tegenstelling tot Google Drive of iCloud kan Proton uw bestanden niet lezen: ze worden versleuteld voordat ze uw apparaat verlaten. Zwitserse jurisdictie, hetzelfde account als Proton Mail.
|
||||
Iedereen, als eenvoudige, directe vervanging van de cloud van Google of Apple.
|
||||
Activeer Proton Drive in uw Proton-account en installeer de app.
|
||||
Schakel automatische back-up van uw foto's in, en schakel die van Google/Apple uit.
|
||||
proton.me/drive →🟢🟡
|
||||
Cy
|
||||
Cryptomator
|
||||
🟡 GevorderdVersleutelt vóór upload🇩🇪 · open-source
|
||||
Een kluis die uw bestanden vóór het uploaden versleutelt, naar elke cloud, zelfs Google Drive of Dropbox.
|
||||
De perfecte truc als u een bestaande cloud moet behouden: u houdt de handige dienst, maar de provider ziet alleen versleutelde brij. Alleen u heeft de sleutel.
|
||||
Wie nog niet van een grote publieke cloud af kan, maar zijn bestanden nu al wil beschermen.
|
||||
Installeer Cryptomator (desktop en mobiel) vanaf cryptomator.org.
|
||||
Maak een "kluis" aan in de gesynchroniseerde map van uw cloud, kies een sterk wachtwoord.
|
||||
Plaats uw bestanden in de kluis: ze worden versleuteld en automatisch gesynchroniseerd.
|
||||
cryptomator.org →🟡🔴
|
||||
Nc
|
||||
Nextcloud
|
||||
🔴 ExpertUw eigen cloud🌍 · open-source
|
||||
Uw eigen cloud (bestanden, agenda, contacten, notities) gehost op uw eigen server.
|
||||
De ultieme graad van controle: uw gegevens verlaten nooit uw hardware. Gedetailleerd in sectie 10 (zelfhosting).
|
||||
Zelf-hosters. Zie de volledige kaart in sectie 10.
|
||||
→ Sectie 10nextcloud.com →🔴
|
||||
DEEL 07 · 🟡
|
||||
Wachtwoordmanager
|
||||
Eén sterk, uniek wachtwoord per dienst: de basis van alle beveiliging die volgt. Weg met het notitieboekje, weg met het hergebruikte wachtwoord.
|
||||
Bw
|
||||
Bitwarden
|
||||
🟢 BeginnerBeste om mee te starten🌍 · open-source
|
||||
Een wachtwoordkluis die uw wachtwoorden onthoudt, genereert en voor u invult, op al uw apparaten.
|
||||
Gratis, geauditeerd, platformonafhankelijk en end-to-end versleuteld. U hoeft nog maar één sterk wachtwoord te onthouden. Hij is zelfs zelf-hostbaar (via Vaultwarden), zodat u van niemand afhankelijk bent.
|
||||
Iedereen. Het is de basis van alle beveiliging: één sterk, uniek wachtwoord per dienst.
|
||||
Maak een account aan op bitwarden.com en kies een lang hoofdwachtwoord (een wachtwoordzin). Vergeet het nooit: het kan niet worden hersteld.
|
||||
Installeer de browserextensie en de mobiele app.
|
||||
Laat Bitwarden terwijl u inlogt uw oude wachtwoorden opslaan en ze vervangen door gegenereerde wachtwoorden.
|
||||
bitwarden.com →🟢🟡🔴
|
||||
Kp
|
||||
KeePassXC
|
||||
🔴 Expert100% lokaal🌍 · open-source
|
||||
Een lokale kluis: een versleuteld bestand dat alleen u bezit, zonder cloud en zonder account.
|
||||
Niets verlaat uw apparaat, tenzij u er zelf voor kiest het bestand te synchroniseren (bijvoorbeeld via Cryptomator). De keuze van de purist, zonder enige tussenpersoon.
|
||||
Gevorderde profielen die volledige controle willen en nul online-afhankelijkheid.
|
||||
Installeer KeePassXC (desktop) en een compatibele mobiele app (KeePassDX op Android, Strongbox op iOS).
|
||||
Maak een databasebestand dat met een hoofdwachtwoord is beveiligd.
|
||||
Om het op alle apparaten te hebben, synchroniseert u dit bestand via een versleutelde cloud.
|
||||
keepassxc.org →🔴
|
||||
Pp
|
||||
Proton Pass
|
||||
🟢 BeginnerMet e-mailaliassen🇨🇭 · open-source
|
||||
De wachtwoordmanager uit het Proton-ecosysteem, met ingebouwde wegwerpbare e-mailaliassen.
|
||||
Ideaal als u al bij Proton zit: één account voor mail, cloud en wachtwoorden. De e-mailaliassen besparen u overal uw echte adres te geven.
|
||||
Wie alles bij Proton wil bundelen.
|
||||
proton.me/pass →🟢🟡
|
||||
BEVEILIGING · 2FA · 🟡
|
||||
Tweestapsverificatie & hardwaretokens
|
||||
Een wachtwoord, zelfs een sterk, kan uitlekken. Tweestapsverificatie (2FA) voegt een tweede bewijs toe bij het inloggen: zelfs als uw wachtwoord is gestolen, blijft het account vergrendeld. Het is essentieel op uw gevoelige accounts (e-mail, wachtwoordmanager, sociale media).
|
||||
Vermijd 2FA via sms
|
||||
Een code per sms is de zwakke schakel: hij is kwetsbaar voor "SIM-swap" (een aanvaller laat uw nummer overzetten) en voor interceptie via het telefoonnetwerk (SS7). Studies schatten dat de meeste SIM-swap-pogingen slagen. Gebruik sms alleen als laatste redmiddel, nooit als uw belangrijkste bescherming.
|
||||
Yk
|
||||
YubiKey · Nitrokey
|
||||
🟡🔴 Gevorderd / ExpertFIDO2-hardwaretoken
|
||||
Een kleine fysieke sleutel (USB, vaak met NFC) die uw identiteit bewijst met één aanraking.
|
||||
Dit is de sterkste 2FA: hij is phishing-bestendig, omdat de sleutel het echte adres van de site controleert voordat hij antwoordt. Nitrokey is het volledig open alternatief qua hardware en firmware. Een nepsite kan u niet misleiden.
|
||||
Wie maximale beveiliging op sleutelaccounts wil. Koop er twee (één hoofd, één back-up).
|
||||
Schaf twee sleutels aan via yubico.com of nitrokey.com.
|
||||
Voeg in de beveiligingsinstellingen van elk account (e-mail, wachtwoordmanager…) een "beveiligingssleutel" of "passkey" toe. Registreer beide sleutels.
|
||||
Bewaar de back-upsleutel op een veilige, afzonderlijke plek.
|
||||
yubico.com →nitrokey.com →🟡🔴
|
||||
Ae
|
||||
Aegis · Ente Auth
|
||||
🟢 BeginnerTOTP-codes🌍 · open-source
|
||||
Apps die elke 30 seconden de zescijferige codes genereren, ter vervanging van Google Authenticator.
|
||||
Aegis (Android) bewaart uw codes in een lokale versleutelde kluis, geen cloud. Ente Auth voegt end-to-end versleutelde synchronisatie tussen apparaten toe, en is geauditeerd. Veel veiliger dan sms, en gratis.
|
||||
Iedereen: dit is het standaard 2FA-niveau om aan te nemen, vóór hardwaretokens.
|
||||
Installeer Aegis (F-Droid) of Ente Auth (alle platforms).
|
||||
Schakel op elk account 2FA via een "authenticator-app" in en scan de getoonde QR-code.
|
||||
Noteer de back-upcodes op papier en maak een versleutelde back-up van uw kluis.
|
||||
getaegis.app →ente.io/auth →🟢🟡
|
||||
DEEL 08 · 🟡
|
||||
Gedecentraliseerde sociale netwerken
|
||||
Instagram, X en Facebook zijn van bedrijven die u profileren en u van de ene op de andere dag kunnen censureren of verwijderen. Het "fediverse" biedt netwerken die van hun gebruikers zijn.
|
||||
Ma
|
||||
Mastodon
|
||||
🟢 BeginnerVervangt X / Twitter🌍 · open-source
|
||||
Het alternatief voor X/Twitter: een sociaal netwerk dat bestaat uit duizenden onafhankelijke servers die met elkaar communiceren (het "fediverse").
|
||||
Geen enkele eigenaar, geen algoritme dat voor u beslist, geen willekeurige ban door één bedrijf. De tijdlijn is chronologisch, zonder advertenties of manipulatie.
|
||||
Iedereen. Ideaal voor informatieaccounts die een aanwezigheid willen die geen enkel bedrijf kan afsnijden.
|
||||
Kies op joinmastodon.org een server (of "instance") die bij u past, en maak een account aan.
|
||||
Installeer een app (de officiële, of Ivory, Tusky…) en volg accounts: u ziet het hele fediverse, op welke server ze ook zitten.
|
||||
joinmastodon.org →🟢🟡
|
||||
No
|
||||
Nostr
|
||||
🟡🔴Oncensureerbaar🌍 · open protocol
|
||||
Geen app maar een protocol: een sociaal netwerk waarin uw identiteit een eenvoudig sleutelpaar is, zoals bij Bitcoin.
|
||||
Geen e-mail, geen wachtwoord, geen account dat iemand kan verwijderen. Uw berichten reizen via gedecentraliseerde relays en u bezit uw publiek echt: niemand kan u blijvend verbannen. U kunt zelfs fooien in Bitcoin ontvangen ("zaps") en op elk moment van app wisselen zonder uw volgers te verliezen.
|
||||
Informatieaccounts en makers die platformcensuur vrezen en een echt eigen aanwezigheid willen.
|
||||
Installeer een client: Damus (iPhone), Amethyst of Primal (Android), of een webversie.
|
||||
De app genereert uw sleutelpaar. Maak een back-up van uw privésleutel (nsec) zoals een Bitcoin-herstelzin: op papier, nooit delen.
|
||||
Maak uw profiel aan, volg mensen, schakel zaps in om sats te ontvangen.
|
||||
nostr.com →🟡🔴
|
||||
El
|
||||
Matrix / Element
|
||||
🟡🔴Vervangt Discord / Slack🌍 · gefedereerd · E2EE
|
||||
Communitychat, in de geest van Discord of Slack, maar dan gefedereerd en end-to-end versleutelbaar. Element is de app, Matrix is het netwerk.
|
||||
U kunt uw eigen server hosten (Synapse) en uw gegevens beheren. Perfect voor een collectief, een redactie of een vereniging die een eigen onafhankelijke ruimte wil.
|
||||
Gemeenschappen en groepen. Zie de metadata-kanttekening hieronder.
|
||||
Installeer Element (element.io) en maak een account aan op een publieke server, of op uw eigen (sectie 10).
|
||||
Word lid van of maak ruimtes, en schakel end-to-end-encryptie in voor privégesprekken.
|
||||
element.io →🟡🔴
|
||||
Pi
|
||||
Pixelfed · PeerTube · Lemmy
|
||||
🟡Insta · YouTube · Reddit🌍 · open-source
|
||||
De gefedereerde neven van Instagram (Pixelfed), YouTube (PeerTube) en Reddit (Lemmy).
|
||||
Dezelfde logica als Mastodon: geen enkele eigenaar, geen advertentie-algoritme, geen tracking. U plaatst foto's, video's of discussies zonder een profileringsmachine te voeden.
|
||||
Wie Instagram, YouTube of Reddit wil verlaten zonder het formaat op te geven.
|
||||
pixelfed.org →peertube →lemmy →🟡
|
||||
Metadata-kanttekening
|
||||
Matrix versleutelt de inhoud goed, maar in federatie zien deelnemende servers het lidmaatschap van ruimtes en de tijdstempels van berichten. Voor zeer gevoelig gebruik kiest u liever SimpleX/Session of host u uw eigen server.
|
||||
DEEL 09 · 🟡
|
||||
Financiële soevereiniteit
|
||||
Ook geld wordt in de gaten gehouden en kan worden gecensureerd. Accounts van activisten zijn bevroren, donaties geblokkeerd, en elke kaartbetaling wordt gevolgd. Financiële privacy maakt deel uit van soevereiniteit.
|
||||
₿
|
||||
Bitcoin (zelfbewaring)
|
||||
🟡🔴Censuurbestendig
|
||||
Een digitaal geld dat u zelf bewaart, zonder via een bank te gaan.
|
||||
Bij zelfbewaring (uw sleutels in een portemonnee die alleen u beheert) kan niemand uw tegoeden bevriezen of blokkeren. De privacy is niet perfect (het grootboek is openbaar), maar het ontsnapt aan bankcensuur, handig op de dag dat een betaling wordt geweigerd.
|
||||
Zich beschermen tegen het bevriezen van accounts of de-banking, en anoniem betalen voor een dienst zoals een VPN.
|
||||
Schaf bitcoins aan (via een exchange, of peer-to-peer).
|
||||
Verplaats ze meteen naar een portemonnee die u beheert: een hardwareportemonnee (Trezor, ColdCard) of een open-source-app. Laat ze niet op de exchange staan.
|
||||
Maak een back-up van uw herstelzin op papier, nooit als foto of online.
|
||||
bitcoin.org →🟡🔴
|
||||
ɱ
|
||||
Monero (XMR)
|
||||
🔴 ExpertStandaard privé
|
||||
De privacycryptocurrency: bedragen, afzender en ontvanger zijn standaard verborgen.
|
||||
Het is op dit punt het exacte tegenovergestelde van Bitcoin: transacties zijn privé per ontwerp. Het is de beste manier om een dienst te betalen zonder een spoor naar u achter te laten.
|
||||
Wie maximale financiële privacy wil. Let op: Monero is al van verschillende Europese exchanges gehaald, dus u heeft vaak een gedecentraliseerde swap nodig.
|
||||
Installeer een portemonnee (de officiële op getmonero.org, of Cake Wallet op mobiel).
|
||||
Verkrijg XMR via een gedecentraliseerde exchange-dienst (bijvoorbeeld een "swap" vanuit een andere crypto).
|
||||
Maak een back-up van uw herstelzin op papier.
|
||||
getmonero.org →🔴
|
||||
Wettigheid & voorzichtigheid
|
||||
Financiële privacy is legaal. Verwar het niet met belastingontduiking: geef op wat opgegeven moet worden. Cryptocurrencies zijn volatiel en oplichting komt veel voor; zet nooit in wat u niet kunt verliezen, en leer voordat u handelt.
|
||||
BLINDE VLEK · DE AI-WAANZIN · 🟡
|
||||
Conversationele AI
|
||||
Terwijl we vechten om onze berichten te versleutelen, geven honderden miljoenen mensen hun meest intieme gedachten in ChatGPT, Gemini of Copilot, op Amerikaanse servers. Dit is de blinde vlek van het hele verhaal. Pure waanzin.
|
||||
Wat u in een cloud-AI typt, is een geschreven, van een tijdstempel voorziene, opgeslagen bekentenis. Erger dan een bericht: u stort er uw angsten, gezondheid, financiën, werkgeheimen in uit, vaak eerlijker dan bij een vriend. En in tegenstelling tot een versleutelde messenger is alles in platte tekst leesbaar op de server.
|
||||
Wat de feiten zeggen (2025-2026)
|
||||
Consumentenabonnementen trainen standaard op uw gegevens. In januari 2026 beval een Amerikaanse rechter OpenAI om 20 miljoen ChatGPT-gesprekken als bewijs te overleggen: de getroffen gebruikers werden niet geïnformeerd noch geraadpleegd. Een gesprek verwijderen garandeert niet dat het weg is. Bij één lek bevatten 47.000 blootgelegde gesprekken e-mails, telefoonnummers en herleidbare intieme details.
|
||||
De tegenzet: lokale AI
|
||||
Ai
|
||||
Ollama · LM Studio · Jan · GPT4All
|
||||
🟡🔴Vervangt ChatGPT🌍 · 100% lokaal
|
||||
Software om een AI-model rechtstreeks op uw eigen computer te draaien.
|
||||
Uw vragen verlaten het apparaat nooit: geen server, geen account, geen telemetrie, en het werkt offline. De kwaliteit hangt af van uw hardware, maar er lekt niets. Ollama en LM Studio zijn het eenvoudigst; Jan en GPT4All mikken op maximale privacy.
|
||||
Iedereen die AI gebruikt voor zelfs maar enigszins persoonlijke onderwerpen en ze niet aan een Amerikaanse server wil toevertrouwen.
|
||||
Installeer voor een zachte start LM Studio of Jan (een grafische interface, als een gewone app).
|
||||
Download een model dat in de app wordt aangeboden (bijvoorbeeld Llama of Mistral), afgestemd op de kracht van uw machine.
|
||||
Chat weg: alles gebeurt lokaal. Ollama is de opdrachtpromptversie voor de meer technische gebruikers.
|
||||
lmstudio.ai →jan.ai →ollama.com →🟡🔴
|
||||
☁
|
||||
Duck.ai · Lumo (Proton)
|
||||
🟢🟡Privacybewuste cloud
|
||||
Cloud-AI-toegang, maar dan gebouwd voor privacy, wanneer een lokale AI niet mogelijk is.
|
||||
Duck.ai (DuckDuckGo) biedt geanonimiseerde toegang tot meerdere modellen. Lumo (Proton) versleutelt de uitwisselingen en traint er niet op. Veel beter dan consumenten-ChatGPT, maar het blijft een server op afstand.
|
||||
Wie de gemakken van de cloud wil zonder de reuzen te voeden. Houd niet-gevoelige onderwerpen daar.
|
||||
duck.ai →lumo →🟢🟡
|
||||
De simpele regel
|
||||
Plak nooit iets in een cloud-AI dat u niet zou vertellen aan een vreemde die meeluistert: identiteit, gezondheid, wachtwoorden, werkgeheimen, bekentenissen. Voor al het andere dat gevoelig is, gebruikt u een lokale AI.
|
||||
VERVANG DE REST · 🟡
|
||||
De dagelijkse gereedschapskist
|
||||
Google en Apple zijn niet alleen e-mail: kaarten, notities, agenda, foto's, videogesprekken, alles is aan uw identiteit gekoppeld. Hier leest u hoe u elke bouwsteen één voor één vervangt, zonder in te leveren op gemak.
|
||||
Om
|
||||
Organic Maps · OsmAnd
|
||||
🟢 BeginnerVervangt Google Maps🌍 · open-source
|
||||
Kaart- en navigatie-apps, gebouwd op OpenStreetMap, die offline werken.
|
||||
Google Maps legt elke reis vast en bouwt een griezelig nauwkeurige locatiegeschiedenis op. Organic Maps volgt niets en toont geen advertenties; OsmAnd is gericht op gevorderde gebruikers (wandelen, hoogtelijnen, gedetailleerde navigatie).
|
||||
Iedereen. Live verkeersinformatie en openbaar vervoer zijn minder compleet dan bij Google, maar de dagelijkse navigatie is uitstekend.
|
||||
Installeer vanuit F-Droid, de App Store of Google Play.
|
||||
Download vooraf de kaarten van de regio's die u nodig heeft voor offline gebruik.
|
||||
organicmaps.app →osmand.net →🟢
|
||||
No
|
||||
Standard Notes · Joplin · CryptPad
|
||||
🟢🟡Vervangt Docs / Notion🌍 · open-source
|
||||
End-to-end versleutelde notities en documenten, ter vervanging van Google Docs, Notion of Evernote.
|
||||
Standard Notes (van Proton) versleutelt uw notities standaard. Joplin beheert Markdown-notitieboeken met optionele versleuteling en de synchronisatie van uw keuze. CryptPad (Frans) biedt een real-time, volledig versleuteld Google Docs-alternatief.
|
||||
Iedereen voor notities; CryptPad voor samenwerking met meerdere personen.
|
||||
Standard Notes / Joplin: maak een account aan, installeer de app en schakel versleuteling in (standaard aan bij Standard Notes, aan te zetten in Joplin).
|
||||
CryptPad: gebruik cryptpad.fr of een andere instance, geen account nodig om te beginnen.
|
||||
standardnotes.com →joplinapp.org →cryptpad.org →🟢🟡
|
||||
Vi
|
||||
Jitsi Meet · Element Call
|
||||
🟢🟡Vervangt Zoom / Meet🌍 · open-source
|
||||
Videoconferentietools zonder account of zware installatie, ter vervanging van Zoom, Google Meet of Teams.
|
||||
Jitsi Meet draait in de browser, geen account, en kan zelf worden gehost (het is ook de basis van Brave Talk). Element Call is end-to-end versleuteld en gefedereerd via Matrix. Voor een eenvoudig, volledig versleuteld gesprek voldoen Signal-gesprekken ook.
|
||||
Iedereen. Op een publieke Jitsi-server zijn gesprekken met meerdere deelnemers in het transport versleuteld; end-to-end-encryptie is optioneel.
|
||||
Jitsi: ga naar meet.jit.si, maak een kamernaam aan, deel de link. Niets te installeren.
|
||||
Element Call: start vanuit de Element (Matrix)-app een gesprek in een ruimte.
|
||||
jitsi.org →element.io/element-call →🟢🟡
|
||||
Ph
|
||||
Ente · Immich
|
||||
🟡🔴Vervangt Google Photos🌍 · open-source
|
||||
Twee manieren om uw foto's te bewaren zonder ze aan Google of Apple toe te vertrouwen.
|
||||
Ente is een beheerde, end-to-end versleutelde dienst, met herkenning en zoeken op het apparaat: eenvoudig en veilig. Immich is een Google Photos-kloon die u zelf host (inclusief gezichts- en objectherkenning), voor wie een server kan beheren.
|
||||
Ente voor iedereen; Immich voor zelf-hosters. Let op: Immich is niet end-to-end versleuteld, de beveiliging hangt af van uw server.
|
||||
Ente: installeer de app, maak een account aan en schakel automatische back-up van uw fotorol in.
|
||||
Immich: zet het via Docker op uw server (zie sectie 10) en koppel de mobiele app.
|
||||
ente.io →immich.app →🟡🔴
|
||||
@
|
||||
SimpleLogin · addy.io
|
||||
🟢🟡E-mailaliassen🌍 · open-source
|
||||
Wegwerpbare e-mailaliassen: een uniek adres per site, dat doorstuurt naar uw echte inbox zonder deze ooit te onthullen.
|
||||
U geeft niet langer overal uw echte adres op. Als een site wordt gehackt of u spamt, knipt u de bijbehorende alias en weet u meteen wie uw gegevens heeft gelekt. SimpleLogin is geïntegreerd met Proton; addy.io biedt onbeperkte aliassen in het gratis abonnement.
|
||||
Iedereen, en vooral pseudoniem-accounts die hun aanmeldingen willen compartimenteren.
|
||||
Maak een account aan, installeer de browserextensie.
|
||||
Genereer bij elke aanmelding een nieuwe alias in plaats van uw echte adres in te typen.
|
||||
simplelogin.io →addy.io →🟢🟡
|
||||
Bk
|
||||
Versleutelde back-ups
|
||||
🟡🔴Cryptomator · restic🌍 · open-source
|
||||
Een manier om een back-up te maken van uw telefoon en computer, waarbij de gegevens vóór vertrek naar de cloud worden versleuteld.
|
||||
iCloud/Google-back-ups zijn vaak toegankelijk voor de provider, en dus voor rechtbanken. Cryptomator versleutelt uw bestanden vóór ze naar een cloud worden gestuurd; restic maakt automatische versleutelde back-ups; Proton Drive is end-to-end versleuteld. Schakel op de iPhone "Geavanceerde gegevensbescherming" in om uw iCloud-back-ups te versleutelen.
|
||||
Iedereen zou zijn back-ups moeten versleutelen. Gouden regel: twee versleutelde kopieën, op twee media, op twee locaties.
|
||||
iPhone: Instellingen > uw naam > iCloud > Geavanceerde gegevensbescherming: schakel in.
|
||||
PC: installeer Cryptomator, maak een kluis aan, plaats er uw bestanden in vóór synchronisatie met de cloud.
|
||||
cryptomator.org →restic.net →🟡🔴
|
||||
RG
|
||||
Wis uw gegevens (AVG)
|
||||
🟡Databrokers
|
||||
Uw persoonlijke gegevens laten verwijderen uit de "databrokers" die ze verzamelen en doorverkopen.
|
||||
In Europa geeft de AVG (artikel 17) u een recht op gegevenswissing: u kunt gratis eisen dat een bedrijf uw gegevens verwijdert, in principe binnen een maand. Diensten als Incogni automatiseren deze verzoeken op grote schaal, tegen betaling.
|
||||
Wie zijn blootstelling wil verminderen. Let op: gegevens komen na verloop van tijd terug, en dit raakt geen openbare registers of sociale media. Het zelf doen is gratis maar tijdrovend.
|
||||
Voor een handmatig verzoek: schrijf de broker, verwijs naar AVG-artikel 17 en vraag om verwijdering.
|
||||
Om te automatiseren: abonneer u op een verwijderservice en laat die de verzoeken namens u versturen.
|
||||
cnil.fr →incogni.com →🟡
|
||||
DEEL 10 · GEAVANCEERDE ZELFVERDEDIGING · 🔴
|
||||
Zelfhosting
|
||||
Het ultieme niveau van soevereiniteit: host uw diensten zelf. Uw gegevens leven op uw eigen hardware, onder de jurisdictie die u kiest.
|
||||
Yh
|
||||
YunoHost · Umbrel · Start9
|
||||
🔴 ExpertKant-en-klare server🌍 · open-source
|
||||
Systemen die een oude pc of Raspberry Pi omtoveren tot een persoonlijke server, met een app-catalogus die u in een paar klikken installeert.
|
||||
Zelfhosting klinkt eng, maar deze tools doen het zware werk. YunoHost beheert zelfs uw eigen e-mail en single sign-on. Umbrel biedt een app-store met meer dan 300 apps, populair bij privacy- en Bitcoin-liefhebbers. Start9 versleutelt back-ups en geeft elke dienst standaard een Tor-adres.
|
||||
Gevorderde profielen die een middag willen besteden aan het opzetten van hun eigen cloud. Het is de meest radicale structurele tegenzet tegen Chat Control.
|
||||
Pak een oude computer, een Raspberry Pi, of huur een kleine server (VPS) in een beschermend land.
|
||||
Installeer YunoHost, Umbrel of Start9 volgens hun officiële handleiding.
|
||||
Installeer vanuit de catalogus Nextcloud, een Matrix-server, een fotogalerij, enz.
|
||||
Bereik het privé met Tailscale (kaart hieronder) in plaats van de server bloot te stellen aan het publieke internet.
|
||||
yunohost.org →umbrel.com →start9.com →🔴
|
||||
Nc
|
||||
Nextcloud
|
||||
🔴 ExpertVervangt de hele Google-suite🇩🇪 · open-source
|
||||
Uw eigen cloud: bestanden, agenda, contacten, foto's, notities en samenwerkingsdocumenten, op uw server.
|
||||
Het is de volledige vervanging van Google Drive, Agenda en Contacten, maar dan thuis. U kiest de jurisdictie en niemand anders heeft zeggenschap over uw bestanden. Let op: het is "uw server"-privacy, niet standaard end-to-end versleuteld; u moet het up-to-date houden.
|
||||
Zelf-hosters. De eenvoudigste weg is installatie via YunoHost of Umbrel.
|
||||
nextcloud.com →🔴
|
||||
Mx
|
||||
Matrix Synapse
|
||||
🔴 ExpertUw eigen chatserver🌍 · open-source
|
||||
Uw eigen Matrix-chatserver, waarop Element en versleutelde gesprekken draaien.
|
||||
Door de server te hosten, beheert u ook de metadata (wie met wie praat, wanneer), het zwakke punt van Matrix in federatie. Het is communitychat waarvan u alleen de eigenaar bent.
|
||||
Gemeenschappen, collectieven, redacties die hun eigen onafhankelijke gefedereerde ruimte willen.
|
||||
element.io/server →🔴
|
||||
Ts
|
||||
Tailscale · Headscale · WireGuard
|
||||
🔴 ExpertPrivétoegang tot uw server🌍 · open-source
|
||||
Een privé versleuteld netwerk dat uw apparaten aan uw server koppelt, zonder deze ooit bloot te stellen aan het publieke internet.
|
||||
In plaats van poorten te openen (en aangevallen te worden), bouwt Tailscale een WireGuard-tunnel tussen alleen uw geautoriseerde apparaten: uw server heeft geen publiek aanvalsoppervlak. Headscale is de zelf-gehoste versie, zodat u van geen enkele derde partij afhankelijk bent. WireGuard alleen biedt volledige controle, ten koste van handmatige configuratie.
|
||||
Elke zelf-hoster. Het is de veilige manier om van buitenaf uw Nextcloud of foto's te bereiken.
|
||||
Installeer Tailscale op uw server en op uw telefoon/pc, en koppel ze aan hetzelfde account.
|
||||
Uw apparaten zien elkaar meteen op een privénetwerk; bereik uw server via het Tailscale-adres.
|
||||
Voor nul afhankelijkheid vervangt u de coördinatieserver door zelf-gehoste Headscale.
|
||||
tailscale.com →headscale.net →🔴
|
||||
www
|
||||
Uw eigen website
|
||||
🟡🔴Ban-bestendig uw stem
|
||||
Een eigen site, op uw eigen domeinnaam, waar uw inhoud van geen enkel platform afhangt.
|
||||
Op de dag dat een netwerk uw account verwijdert of schorst, verdwijnt al uw werk. Een persoonlijke site is uw anker: uw berichten blijven daar, gearchiveerd en up-to-date, wat er ook gebeurt. Dat is precies wat voorzichtige informatieaccounts doen. Een statische site (gebouwd met Hugo, Astro of Jekyll) is voldoende en publiceert bijna gratis; voor een dynamische blog voldoet een zelf-gehoste WordPress of Ghost.
|
||||
Informatieaccounts, makers, activisten, iedereen die publiceert en platformcensuur vreest.
|
||||
Koop een domeinnaam (idealiter bij een privacyrespecterende registrar).
|
||||
Kies een statische-sitegenerator (Astro, Hugo) en host het resultaat, of installeer WordPress/Ghost op uw server.
|
||||
Verwijs er systematisch naar vanuit uw sociale netwerken: uw publiek vindt u zelfs als een account wegvalt.
|
||||
astro.build →ghost.org →🟡🔴
|
||||
Het jurisdictiepunt
|
||||
Zelfhosten betekent ook kiezen waar uw gegevens leven. Een server thuis of in een beschermend land ontsnapt aan de scanverplichtingen die aan grote platforms worden opgelegd. Het is de meest radicale structurele tegenzet tegen Chat Control.
|
||||
DEEL 11 · 🔴
|
||||
Tor
|
||||
Het netwerk dat scheidt wie u bent van wat u online doet.
|
||||
Tor
|
||||
Tor Browser
|
||||
🔴 ExpertNetwerkanonimiteit🌍 · open-source
|
||||
Een netwerk dat scheidt wie u bent van wat u online doet, toegankelijk via de Tor Browser.
|
||||
Uw verkeer stuitert via meerdere versleutelde relays wereldwijd: geen enkele kent zowel uw identiteit als uw bestemming. Het is het gereedschap van journalisten en bronnen, en de manier om een geblokkeerde site te bereiken (bijvoorbeeld om een gecensureerde VPN te downloaden).
|
||||
Hoogrisicoprofielen, en iedereen die zijn activiteit moet loskoppelen van zijn echte IP. Gebruik het af en toe, niet als dagelijkse browser.
|
||||
Download de Tor Browser van torproject.org (Windows, macOS, Linux, Android).
|
||||
Start hem en klik op "Verbinden". Als Tor in uw land geblokkeerd is, schakel dan een "bridge" in.
|
||||
Blader zonder het venster te maximaliseren, zonder extensies te installeren, en zonder u aan te melden bij uw echte accounts.
|
||||
torproject.org →🔴
|
||||
Beperkingen, geen magie
|
||||
Tor beschermt het transport, niet uw gewoonten: meld u aan bij uw echte account en u de-anonimiseert uzelf. Het is trager, en de exit-node ziet onversleuteld verkeer (gebruik HTTPS). Voor serieuze anonimiteit, combineer het met Tails (sectie 12).
|
||||
DEEL 12 · 🔴
|
||||
Vrije besturingssystemen
|
||||
De hoeksteen. Als client-side scanning kan worden opgelegd door het besturingssysteem zelf, is de enige echte tegenzet dat besturingssysteem beheersen. Dat geldt zowel voor de telefoon (GrapheneOS) als voor de computer (Linux). Onderschat dit niet: zolang u op Windows, macOS of een Google-Android blijft, beheerst u uw machine niet echt.
|
||||
Gr
|
||||
GrapheneOS
|
||||
🔴 ExpertOntgooglede mobielAndroid · Pixel · open-source
|
||||
Een volledig ontgooglede Android, geïnstalleerd op een Pixel-telefoon: mobiele soevereiniteit.
|
||||
Het is de structurele verdediging bij uitstek tegen een besturingssysteem of app-store die u zou willen scannen. Google-diensten in een optionele sandbox, netwerkpermissies per app, geïsoleerde profielen, automatische herstart die sleutels uit het geheugen wist, een dwang-PIN die de telefoon wist. Zo gerespecteerd om zijn beveiliging dat het wordt gebruikt door spyware-doelen.
|
||||
Blootgestelde profielen, maar ook elke burger die een Pixel wil kopen om de controle over zijn telefoon terug te nemen.
|
||||
Schaf een compatibele Google Pixel-telefoon aan (de enige ondersteunde hardware, om veiligheidsredenen).
|
||||
Ga op een computer naar grapheneos.org/install en volg de webinstaller (een paar klikken, geen terminal nodig).
|
||||
Installeer voor uw apps de vrije F-Droid-winkel; voeg alleen indien nodig Google Play in een sandbox toe.
|
||||
Stel een lange wachtwoordzin in (geen 4-cijferige PIN) en leer de "Lockdown"-modus.
|
||||
grapheneos.org →🔴
|
||||
🐧
|
||||
Linux (op desktop)
|
||||
🟡🔴Vervangt Windows / macOS🌍 · vrij & gratis
|
||||
Een vrij en gratis besturingssysteem voor uw computer, ter vervanging van Windows of macOS. Het bestaat in vele smaken, "distributies" genaamd.
|
||||
Het is de blinde vlek van veel mensen: ze versleutelen hun berichten, maar houden een Windows die onophoudelijk telemetrie naar Microsoft streamt (wiens "Recall"-functie het scherm continu probeerde te fotograferen), of een macOS die rapporteert welke apps u opent. Linux bespioneert u niet, is gratis, doet oude computers herleven, en maakt u eindelijk meester van uw machine. Het is eenvoudiger dan zijn reputatie: moderne distributies lijken op Windows of macOS.
|
||||
Iedereen kan de overstap maken. Om te beginnen: Linux Mint (het dichtst bij Windows) of Fedora; Debian voor stabiliteit. U hoeft niets te wissen: u kunt eerst testen en daarnaast uw huidige systeem installeren.
|
||||
Niet zeker welke distributie? Beantwoord de distrochooser.de/fr-vragenlijst, die u op de juiste weg helpt op basis van uw behoeften.
|
||||
Probeer het zonder iets te installeren, rechtstreeks in uw browser, op distrosea.com.
|
||||
Eenmaal overtuigd, download de image van de distributie en maak een opstartbare USB (met Ventoy of Balena Etcher).
|
||||
Start vanaf de USB om het systeem "live" te proberen, voer dan de installatie uit. Schakel de aangeboden schijfversleuteling in.
|
||||
distrochooser.de/fr →distrosea.com →linuxmint.com →🟡🔴
|
||||
Ta
|
||||
Tails
|
||||
🔴 ExpertAmnesische USB🌍 · alles via Tor
|
||||
Een systeem dat opstart vanaf een USB-stick, al het verkeer via Tor leidt en bij het afsluiten geen spoor achterlaat.
|
||||
Bij het afsluiten wordt alles vergeten: het is "amnesisch". Ideaal voor gevoelig, eenmalig werk op een computer die niet van u is, zonder iets achter te laten.
|
||||
Klokkenluiders, journalisten, bronnen. Het referentiegereedschap voor werk met hoog risico.
|
||||
Download Tails van tails.net en volg de wizard om de USB-stick te maken.
|
||||
Start de computer opnieuw op vanaf die USB. Gebruik het, en sluit af: alles verdwijnt.
|
||||
tails.net →🔴
|
||||
Qb
|
||||
Qubes OS · postmarketOS
|
||||
🔴 ExpertCompartimentering🌍 · open-source
|
||||
Twee nichesystemen: Qubes compartimenteert uw pc in verzegelde virtuele machines; postmarketOS draait Linux op oude telefoons.
|
||||
Qubes isoleert elke activiteit (werk, bankieren, risicovol browsen) in een eigen compartiment: een compromittering bereikt de rest niet. postmarketOS verlengt de levensduur van telefoons die door hun fabrikant zijn afgedankt.
|
||||
Expertgebruikers met een zeer hoog dreigingsmodel.
|
||||
qubes-os.org →postmarketos.org →🔴
|
||||
Hard uw apparaat
|
||||
Zonder van besturingssysteem te wisselen wint u al veel: installeer apps via F-Droid (vrije winkel) of Aurora Store, vervang Google-diensten door microG, maak gescheiden profielen, knip onnodige netwerkpermissies, schakel automatische cloud-back-up en de advertentie-ID uit.
|
||||
HARDWARE · 🔴
|
||||
Telefonie & fysiek apparaat
|
||||
De beste app is nutteloos als het apparaat zelf u verraadt. Hier zijn de hardwaredreigingen en de stappen die ze neutraliseren.
|
||||
Het telefoonnummer is een tracker
|
||||
Uw nummer verbindt uw SIM-kaart, uw apparaat (de IMEI-identifier), uw locatie en uw identiteit (de meeste landen eisen identiteitsbewijs bij aankoop van een SIM). Het is de draad die alles aan elkaar knoopt.
|
||||
Gebruik nummerloze messengers (SimpleX, Session) voor gevoelige contacten, en geef altijd de voorkeur aan Signal boven sms.
|
||||
Voor een gescheiden identiteit beperkt een "alleen-data"-SIMkaart of prepaid eSIM de koppeling op het verkooppunt (controleer de regels van uw land).
|
||||
IMSI-catchers & het telefoonnetwerk
|
||||
"IMSI-catchers" (of "stingrays") zijn nep-zendmasten die telefoons dwingen verbinding te maken om hun identificatie te registreren en soms communicatie te onderscheppen, vaak door terug te schakelen naar zwak versleuteld 2G. Tegelijk maakt een oude netwerkfout (SS7) het nog steeds mogelijk sms'jes te onderscheppen en telefoons op afstand te lokaliseren: een reden te meer om sms te laten vallen.
|
||||
Schakel 2G uit in de instellingen (mogelijk op Android en GrapheneOS) om de belangrijkste interceptievector te blokkeren.
|
||||
Schakel op een betoging of in een gevoelig gebied de vliegtuigmodus in of stop de telefoon in een Faraday-zak (die alle signalen blokkeert). De EFF publiceert zelfs een vrij hulpmiddel, Rayhunter, om IMSI-catchers te detecteren.
|
||||
Biometrie of toegangscode?
|
||||
Een vinger kan worden afgedwongen, een code niet
|
||||
Uw gezicht of vinger kan op de sensor worden gedrukt terwijl u wordt vastgehouden (aan een grens, tijdens een arrestatie); een wachtwoord in uw hoofd kan dat niet. Dwing vóór een risicovolle situatie een terugkeer naar de toegangscode af: houd op de iPhone de zijknop en een volumeknop ingedrukt (het "SOS"-scherm); gebruik op Android/GrapheneOS Lockdown in het stroommenu, wat biometrie uitschakelt totdat de code is ingevoerd. Kies een lange wachtwoordzin, geen viercijferige PIN. GrapheneOS biedt zelfs een dwang-PIN die de telefoon wist.
|
||||
De-Microsoft, de-Apple
|
||||
Vergeet de computer niet: Windows streamt onophoudelijk telemetrie (en de "Recall"-functie probeerde het scherm continu te fotograferen), macOS rapporteert welke apps u opent. De echte uitweg is Linux (zie sectie 12). Zo niet, schakel dan telemetrie, advertentie-ID en "Recall" uit, en voeg een uitgaande firewall toe (zoals Little Snitch op de Mac).
|
||||
DEEL 13 · 🔴
|
||||
Anonimiteit & OPSEC
|
||||
Voor het pseudoniem-account en de klokkenluider. Hier beschermt u niet langer alleen een bericht: u beschermt een identiteit.
|
||||
Pseudonimiteit versus anonimiteit
|
||||
De gouden regel: kruis uw echte identiteit nooit met uw publieke. Niet hetzelfde e-mailadres, nummer, apparaat, netwerk, posttijdstip of schrijfstijl. Eén lek koppelt de twee.
|
||||
Identiteitloze accounts: SimpleX en Session vragen geen nummer; combineer ze met wegwerpbare e-mailaliassen. Pas op voor doorverkochte, traceerbare "virtuele nummers".
|
||||
Metadata opschonen: een geplaatste foto bevat vaak de datum, het apparaatmodel en soms GPS-coördinaten (EXIF-gegevens). Verwijder ze met Metadata Cleaner, mat2 of ExifTool voordat u iets plaatst; let ook op bestandsnamen en documentmetadata.
|
||||
Netwerk-anti-correlatie: gebruik Tor/Tails om uw activiteit los te koppelen van uw thuis-IP. Meng nooit uw persoonlijke en pseudonimennetwerken. Een SIM op uw naam verraadt uw locatie, wat u ook doet.
|
||||
Compartimentering: een apparaat, of minstens een profiel, gewijd aan de publieke identiteit, een aparte wachtwoordmanager, en nooit kruislings inloggen tussen de twee werelden.
|
||||
Documenten doorspelen als bron
|
||||
SecureDrop is het anonieme indienensysteem dat veel redacties gebruiken om documenten van bronnen te ontvangen. Veel media publiceren ook een PGP-sleutel en een Signal-contact. Dien nooit in vanaf uw werkmaterialen of uw gebruikelijke netwerk.
|
||||
Eerlijkheid, overschat uzelf niet
|
||||
Sterke anonimiteit tegen een staatsvijand is moeilijk en feilbaar. Deze gids geeft bakens, geen garanties. Als er levens van afhangen, train dan met de gezaghebbende bronnen: EFF Surveillance Self-Defense, Freedom of the Press Foundation, Privacy Guides. Strikt defensieve en journalistieke context.
|
||||
DEEL 14 · VERDER GAAN
|
||||
Het complete vrije-software-ecosysteem
|
||||
Volledig ontgoogelen betekent elke bouwsteen vervangen door een vrij equivalent. De gemeenschappelijke logica (vrije software + controle over uw apparaat + zelfhosting) is de echte tegenzet tegen client-side scanning.
|
||||
Kantoorsoftware
|
||||
LibreOffice
|
||||
OnlyOffice
|
||||
Media
|
||||
VLC
|
||||
Jellyfin
|
||||
FreeTube / NewPipe
|
||||
Audacity · OBS
|
||||
GIMP · Inkscape
|
||||
Calibre · Shotcut
|
||||
Cloud & netwerk
|
||||
Nextcloud
|
||||
Pi-hole
|
||||
KDE Connect
|
||||
Vrije mobiel
|
||||
F-Droid · Aurora
|
||||
microG
|
||||
postmarketOS
|
||||
Beveiliging
|
||||
Bitwarden
|
||||
KeePassXC
|
||||
Wireshark
|
||||
E-mail & desktop
|
||||
Thunderbird
|
||||
FairEmail
|
||||
Dev & gevorderd
|
||||
Termux · git
|
||||
QEMU / KVM · Wine
|
||||
Flatpak · GNU/Linux
|
||||
DEEL 15 · IN ACTIE KOMEN
|
||||
Migratie & digitale burgerlijke ongehoorzaamheid
|
||||
Een stapsgewijs plan
|
||||
Week 1 🟢: Installeer Signal en haal uw naasten over. Stap over op Proton Mail of Tuta. Stel een wachtwoordmanager en uBlock Origin in.
|
||||
Week 2 🟡: Migreer bestanden naar een versleutelde cloud, vervang Chrome/Google Search, neem een VPN, open een Mastodon-account.
|
||||
Daarna 🔴: Afhankelijk van uw profiel: GrapheneOS, Tor/Tails, zelfhosting, OPSEC-discipline.
|
||||
Veelgemaakte fouten
|
||||
Denken dat één hulpmiddel genoeg is; uw echte nummer hergebruiken voor een anoniem account; tien apps installeren zonder gewoontes te veranderen; vergeten dat de zwakke schakel vaak het apparaat zelf is.
|
||||
Soevereiniteit is collectief
|
||||
Een versleutelde messenger werkt alleen als de ander hem ook gebruikt. Neem uw contacten, familie en gemeenschap mee. Van hulpmiddel wisselen is stemmen met uw gebruik: iedereen die de surveillanceplatforms verlaat, verzwakt het model.
|
||||
Politiek handelen
|
||||
Techniek vervangt de politiek niet. Volg en steun de mobilisatie: fightchatcontrol.eu, EDRi, het werk van Patrick Breyer. Neem contact op met uw Europarlementariërs, dat is wat de tekst bijna heeft doen vallen.
|
||||
Manifest
|
||||
Illegitieme massasurveillance weigeren is geen verbergen: het is een wettelijke, vreedzame, burgerlijke daad. Privacy is een recht, geen bekentenis. Versleutelen is stemmen. Zelfhosten is ongehoorzaam zijn. Uw hulpmiddelen terugwinnen is onbestuurbaar worden.
|
||||
Bronnen
|
||||
Privacy Guides EFF · Surveillance Self-Defense Freedom of the Press Foundation Fight Chat Control
|
||||
Bronnen · de wet
|
||||
Chat Control · Wikipedia Breyer · vote 9 juillet 2026 Chat Control 1.0 vs 2.0 Proton · client-side scanning Gezamenlijke oproep van de techreuzen (19 mrt 2026) Big Tech vows to keep scanning
|
||||
Handel
|
||||
European Digital Rights (EDRi) Patrick Breyer · Chat Control Signal · blog
|
||||
Open source op GitHub
|
||||
Deze gids is een informatiedocument over privacybescherming en de verdediging van versleuteling. Het raadt geen illegale activiteiten aan. Controleer altijd het wetgevingsnieuws: de situatie verandert snel. Feiten laatst bijgewerkt: 9 juli 2026.
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "exitchatcontrol",
|
||||
"private": true,
|
||||
"version": "3.0.0",
|
||||
"type": "module",
|
||||
"description": "Exit Chat Control — multilingual field guide to escape Chat Control and reclaim digital privacy. Astro static build, zero tracker, zero CDN.",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build && node scripts/build-offline.mjs && node scripts/gen-csp.mjs",
|
||||
"preview": "astro preview",
|
||||
"check": "astro check",
|
||||
"lint": "eslint . --max-warnings 0",
|
||||
"format": "prettier --check .",
|
||||
"test": "vitest run",
|
||||
"test:e2e": "playwright test",
|
||||
"icons": "node scripts/gen-icons.mjs"
|
||||
},
|
||||
"packageManager": "pnpm@10.34.4",
|
||||
"engines": {
|
||||
"node": ">=22.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^7.0.2",
|
||||
"@astrojs/sitemap": "^3.7.3",
|
||||
"astro": "^7.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.9",
|
||||
"@axe-core/playwright": "^4.12.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@types/node": "^26.1.1",
|
||||
"cheerio": "^1.2.0",
|
||||
"eslint": "^10.6.0",
|
||||
"eslint-plugin-astro": "^2.1.1",
|
||||
"globals": "^17.7.0",
|
||||
"prettier": "^3.9.5",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"simple-icons": "^16.25.0",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.63.0",
|
||||
"vitest": "^4.1.10"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
// Two projects: a normal chromium run, and a JS-disabled run — the guide
|
||||
// must be fully readable with JavaScript off (Tor Browser "safest" mode
|
||||
// is part of this site's audience).
|
||||
export default defineConfig({
|
||||
testDir: 'tests/e2e',
|
||||
fullyParallel: true,
|
||||
reporter: [['list']],
|
||||
use: {
|
||||
baseURL: 'http://127.0.0.1:4321',
|
||||
},
|
||||
webServer: {
|
||||
command: 'pnpm preview --host 127.0.0.1 --port 4321',
|
||||
url: 'http://127.0.0.1:4321',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
{
|
||||
name: 'nojs',
|
||||
use: { ...devices['Desktop Chrome'], javaScriptEnabled: false },
|
||||
},
|
||||
],
|
||||
})
|
||||
Generated
-6103
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
Contact: https://github.com/Aurealibe/exitchatcontrol/issues
|
||||
Preferred-Languages: en, fr, nl
|
||||
Expires: 2027-07-01T00:00:00.000Z
|
||||
Canonical: https://exitchatcontrol.org/.well-known/security.txt
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB |
@@ -1,4 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://exitchatcontrol.org/sitemap-index.xml
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "Exit Chat Control",
|
||||
"short_name": "ExitCC",
|
||||
"description": "Multilingual field guide to escape Chat Control and reclaim digital privacy.",
|
||||
"start_url": "/",
|
||||
"display": "browser",
|
||||
"background_color": "#111318",
|
||||
"theme_color": "#e6b23f",
|
||||
"icons": [{ "src": "/favicon.png", "sizes": "192x192", "type": "image/png" }]
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/* Post-build: packs each locale's page into ONE self-contained HTML file —
|
||||
stylesheets and scripts inlined, favicon as a data URI — so the guide can
|
||||
be mirrored, e-mailed, or carried on a USB stick and opened from file://.
|
||||
No parallel implementation: the artifact IS the built page, inlined.
|
||||
Outputs dist/exitchatcontrol-offline[.<locale>].html (contract shared
|
||||
with Footer.astro). */
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const DIST = 'dist'
|
||||
// Locales derive from the dictionary files; the default locale (served at /)
|
||||
// is read from src/i18n/locales.ts — adding a language needs no change here.
|
||||
const DEFAULT = /defaultLocale: Locale = '(\w+)'/.exec(
|
||||
readFileSync('src/i18n/locales.ts', 'utf8'),
|
||||
)?.[1]
|
||||
if (!DEFAULT) throw new Error('[build-offline] cannot read defaultLocale from src/i18n/locales.ts')
|
||||
const LOCALES = readdirSync('src/i18n')
|
||||
.filter((f) => /^[a-z]{2}\.json$/.test(f))
|
||||
.map((f) => f.slice(0, 2))
|
||||
.map((code) => ({
|
||||
code,
|
||||
page: code === DEFAULT ? 'index.html' : `${code}/index.html`,
|
||||
out: `exitchatcontrol-offline${code === DEFAULT ? '' : `.${code}`}.html`,
|
||||
}))
|
||||
|
||||
const faviconDataUri = `data:image/png;base64,${readFileSync('public/favicon.png').toString('base64')}`
|
||||
|
||||
function inlineAssets(html) {
|
||||
// stylesheets → <style>
|
||||
html = html.replace(
|
||||
/<link[^>]+rel="stylesheet"[^>]+href="(\/[^"]+\.css)"[^>]*>/g,
|
||||
(_, href) => `<style>${readFileSync(join(DIST, href), 'utf8')}</style>`,
|
||||
)
|
||||
// module scripts → inline (first occurrence per src; duplicates dropped so
|
||||
// delegated listeners never register twice)
|
||||
const seen = new Set()
|
||||
html = html.replace(
|
||||
/<script([^>]*)\ssrc="(\/[^"]+\.js)"([^>]*)><\/script>/g,
|
||||
(_, pre, src, post) => {
|
||||
if (seen.has(src)) return ''
|
||||
seen.add(src)
|
||||
return `<script${pre}${post}>${readFileSync(join(DIST, src), 'utf8')}</script>`
|
||||
},
|
||||
)
|
||||
// favicon → data URI; PWA/touch links make no sense from file://
|
||||
html = html.replace(/href="\/favicon\.png"/g, `href="${faviconDataUri}"`)
|
||||
html = html.replace(/<link rel="(manifest|apple-touch-icon)"[^>]*>/g, '')
|
||||
return html
|
||||
}
|
||||
|
||||
for (const { code, page, out } of LOCALES) {
|
||||
let html = inlineAssets(readFileSync(join(DIST, page), 'utf8'))
|
||||
// language switcher + brand link → sibling offline artifacts, so the
|
||||
// whole multilingual set works side-by-side from file://
|
||||
for (const sibling of LOCALES) {
|
||||
const route = sibling.code === DEFAULT ? '/' : `/${sibling.code}/`
|
||||
html = html.replaceAll(`href="${route}"`, `href="./${sibling.out}"`)
|
||||
}
|
||||
writeFileSync(join(DIST, out), html)
|
||||
console.log(`[build-offline] ${out} (${code}) — ${(html.length / 1024).toFixed(0)} KB`)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/* Live-checks every citation the guide stands on — timeline, observatory,
|
||||
directory, checklist, allies, footer sources and the narrative sections.
|
||||
404 / DNS failure / 5xx = FAIL; 403/429 (bot walls on europa.eu etc.)
|
||||
are reported as WARN and tolerated.
|
||||
Run: node scripts/check-links.mjs */
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const UA =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36'
|
||||
|
||||
// English sources are canonical — the other locales cite the same URLs.
|
||||
const ROOTS = ['src/data', 'src/i18n/content/en', 'src/content/sections/en', 'src/components']
|
||||
|
||||
function filesUnder(root) {
|
||||
try {
|
||||
if (statSync(root).isFile()) return [root]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
return readdirSync(root, { withFileTypes: true }).flatMap((e) =>
|
||||
e.isDirectory() ? filesUnder(join(root, e.name)) : [join(root, e.name)],
|
||||
)
|
||||
}
|
||||
|
||||
const corpus = ROOTS.flatMap(filesUnder)
|
||||
.map((p) => readFileSync(p, 'utf8'))
|
||||
.join('\n')
|
||||
const urls = [
|
||||
...new Set(
|
||||
[...corpus.matchAll(/https:\/\/[^\s"'`<>)\\]+/g)].map((m) => m[0].replace(/[.,]$/, '')),
|
||||
),
|
||||
]
|
||||
|
||||
let fail = 0
|
||||
let warn = 0
|
||||
|
||||
async function probe(url) {
|
||||
for (const method of ['HEAD', 'GET']) {
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const t = setTimeout(() => controller.abort(), 15_000)
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
redirect: 'follow',
|
||||
signal: controller.signal,
|
||||
headers: { 'user-agent': UA, accept: 'text/html,application/xhtml+xml,*/*' },
|
||||
})
|
||||
clearTimeout(t)
|
||||
if (res.status === 405 && method === 'HEAD') continue // try GET
|
||||
return res.status
|
||||
} catch (e) {
|
||||
if (method === 'GET')
|
||||
return `ERR:${e.name === 'AbortError' ? 'timeout' : (e.cause?.code ?? e.name)}`
|
||||
}
|
||||
}
|
||||
return 'ERR:unknown'
|
||||
}
|
||||
|
||||
console.log(`checking ${urls.length} cited sources…`)
|
||||
const results = await Promise.all(urls.map(async (url) => ({ url, status: await probe(url) })))
|
||||
|
||||
for (const { url, status } of results.sort((a, b) =>
|
||||
String(a.status).localeCompare(String(b.status)),
|
||||
)) {
|
||||
if (typeof status === 'number' && status >= 200 && status < 300) {
|
||||
console.log(` ✓ ${status} ${url}`)
|
||||
} else if (status === 403 || status === 429 || status === 401) {
|
||||
warn++
|
||||
console.log(` ~ ${status} ${url} (bot wall — verify by hand once)`)
|
||||
} else {
|
||||
fail++
|
||||
console.log(` ✗ ${status} ${url}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nlinks: ${urls.length - fail - warn} ok · ${warn} bot-walled · ${fail} broken`)
|
||||
process.exit(fail === 0 ? 0 : 1)
|
||||
@@ -1,45 +0,0 @@
|
||||
// Post-build: compute the Content-Security-Policy for the built site.
|
||||
// Hashes every inline <script> found in dist/**/*.html (the theme/language
|
||||
// boot script, plus anything Astro chose to inline) so the CSP can stay
|
||||
// strict without 'unsafe-inline'. Emits docker/csp.conf (nginx snippet).
|
||||
// Regenerated on every build — the hash can never drift from the markup.
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const DIST = 'dist'
|
||||
|
||||
function htmlFiles(dir) {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const path = join(dir, entry.name)
|
||||
if (entry.isDirectory()) return htmlFiles(path)
|
||||
return entry.name.endsWith('.html') ? [path] : []
|
||||
})
|
||||
}
|
||||
|
||||
const hashes = new Set()
|
||||
const inlineScript = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi
|
||||
|
||||
for (const file of htmlFiles(DIST)) {
|
||||
const html = readFileSync(file, 'utf8')
|
||||
for (const [, body] of html.matchAll(inlineScript)) {
|
||||
if (!body.trim()) continue
|
||||
hashes.add(`'sha256-${createHash('sha256').update(body).digest('base64')}'`)
|
||||
}
|
||||
}
|
||||
|
||||
const scriptSrc = ["'self'", ...hashes].join(' ')
|
||||
const csp = [
|
||||
"default-src 'none'",
|
||||
`script-src ${scriptSrc}`,
|
||||
"style-src 'self'",
|
||||
"img-src 'self' data:",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
"base-uri 'none'",
|
||||
"form-action 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
].join('; ')
|
||||
|
||||
writeFileSync('docker/csp.conf', `add_header Content-Security-Policy "${csp}" always;\n`)
|
||||
console.log(`[gen-csp] ${hashes.size} inline script hash(es) → docker/csp.conf`)
|
||||
@@ -1,72 +0,0 @@
|
||||
/* Generates src/icons.generated.ts from the simple-icons npm package —
|
||||
build-time only, so the site never phones a CDN (v1 loaded brand logos
|
||||
from cdn.simpleicons.org at runtime: a third-party request on a privacy
|
||||
guide). The slug list is DERIVED from the data files (tools.json +
|
||||
directory.json iconSlug fields) so it can never drift from the content.
|
||||
Slugs missing from the installed set fall back to the letter tile at
|
||||
render time. Run: pnpm icons */
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import * as icons from 'simple-icons'
|
||||
|
||||
const dataFiles = ['../src/data/tools.json', '../src/data/directory.json']
|
||||
const slugs = new Set(['github']) // footer link icon
|
||||
for (const file of dataFiles) {
|
||||
const entries = JSON.parse(readFileSync(new URL(file, import.meta.url), 'utf8'))
|
||||
for (const entry of entries) {
|
||||
if (entry.iconSlug) slugs.add(entry.iconSlug)
|
||||
}
|
||||
}
|
||||
|
||||
const bySlug = new Map(Object.values(icons).map((i) => [i.slug, i]))
|
||||
const found = []
|
||||
const missing = []
|
||||
for (const slug of [...slugs].sort()) {
|
||||
const icon = bySlug.get(slug)
|
||||
if (icon) found.push(icon)
|
||||
else missing.push(slug)
|
||||
}
|
||||
|
||||
const lines = found.map((i) => ` ${JSON.stringify(i.slug)}: ${JSON.stringify(i.path)},`)
|
||||
const out = `/* AUTO-GENERATED by scripts/gen-icons.mjs — do not edit.
|
||||
SVG path data from the simple-icons npm package (CC0), inlined at build
|
||||
time. Zero runtime CDN request. Missing slugs at generation time: ${
|
||||
missing.length ? missing.join(', ') : 'none'
|
||||
}. */
|
||||
export const BRAND_ICONS: Record<string, string> = {
|
||||
${lines.join('\n')}
|
||||
}
|
||||
`
|
||||
writeFileSync(new URL('../src/icons.generated.ts', import.meta.url), out)
|
||||
console.log(
|
||||
`icons: ${found.length} embedded, ${missing.length} missing${missing.length ? ` (${missing.join(', ')})` : ''}`,
|
||||
)
|
||||
|
||||
// Brand tile colors as a GENERATED STYLESHEET (not inline style attributes:
|
||||
// the production CSP is style-src 'self', which blocks style= attributes).
|
||||
// Tile ink is picked by WCAG contrast against the brand color — white ink
|
||||
// fails on several light brands.
|
||||
function luminance(hex) {
|
||||
const n = hex.replace('#', '')
|
||||
const [r, g, b] = [0, 2, 4].map((i) => {
|
||||
const c = parseInt(n.slice(i, i + 2), 16) / 255
|
||||
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
||||
})
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
}
|
||||
function inkFor(hex) {
|
||||
const L = luminance(hex)
|
||||
return 1.05 / (L + 0.05) >= (L + 0.05) / 0.05 ? '#faf7ee' : '#14161a'
|
||||
}
|
||||
|
||||
const tools = JSON.parse(readFileSync(new URL('../src/data/tools.json', import.meta.url), 'utf8'))
|
||||
const rules = tools
|
||||
.filter((t) => t.color)
|
||||
.map((t) => `#${t.id} .tool-logo {\n background: ${t.color};\n color: ${inkFor(t.color)};\n}`)
|
||||
const css = `/* AUTO-GENERATED by scripts/gen-icons.mjs — do not edit.
|
||||
Brand tile background + WCAG-picked ink per tool (from tools.json).
|
||||
A stylesheet, not style= attributes: the CSP (style-src 'self')
|
||||
blocks inline styles in production. */
|
||||
${rules.join('\n')}
|
||||
`
|
||||
writeFileSync(new URL('../src/styles/brands.generated.css', import.meta.url), css)
|
||||
console.log(`brands: ${rules.length} tile rules → src/styles/brands.generated.css`)
|
||||
@@ -1,11 +0,0 @@
|
||||
// Renders scripts/og.html (1200×630) to public/og.png via Playwright's
|
||||
// bundled chromium — no hand-rolled browser driving, no image dependency.
|
||||
// Run: node scripts/gen-og.mjs
|
||||
import { chromium } from '@playwright/test'
|
||||
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage({ viewport: { width: 1200, height: 630 } })
|
||||
await page.goto(new URL('./og.html', import.meta.url).href)
|
||||
await page.screenshot({ path: 'public/og.png' })
|
||||
await browser.close()
|
||||
console.log('[gen-og] public/og.png regenerated (1200×630)')
|
||||
-105
File diff suppressed because one or more lines are too long
@@ -1,22 +0,0 @@
|
||||
// Quick visual QA: screenshots a built page region in a given theme.
|
||||
// Usage: node scripts/shot.mjs <url-path> <out.png> [dark|light] [#anchor]
|
||||
// Requires `pnpm preview` (or any server) on 127.0.0.1:4321.
|
||||
import { chromium } from '@playwright/test'
|
||||
|
||||
const [path = '/', out = 'shot.png', theme = 'dark', anchor = ''] = process.argv.slice(2)
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
|
||||
// documentElement doesn't exist yet at init-script time — go through
|
||||
// localStorage, which the theme boot script applies before first paint.
|
||||
await page.addInitScript((t) => localStorage.setItem('ecc-theme', t), theme)
|
||||
await page.goto(`http://127.0.0.1:4321${path}`)
|
||||
if (anchor) {
|
||||
/* global document -- runs inside the browser via page.evaluate */
|
||||
await page.evaluate((id) => {
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: 'instant' })
|
||||
}, anchor.slice(1))
|
||||
await page.waitForTimeout(150)
|
||||
}
|
||||
await page.screenshot({ path: out })
|
||||
await browser.close()
|
||||
console.log(`[shot] ${out} ← ${path}${anchor} (${theme})`)
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
// PART 18 — allied initiatives (src/data/allies.json + per-locale roles):
|
||||
// two grids of 8, the campaigns fighting and the documentation projects
|
||||
// keeping the receipts. Names link out through Ext only.
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import { ALLY_GROUPS, loadDataset } from '../lib/content'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const allies = loadDataset('allies', locale)
|
||||
const groups = ALLY_GROUPS.map((group) => ({
|
||||
group,
|
||||
rows: allies.filter((a) => a.group === group),
|
||||
}))
|
||||
---
|
||||
|
||||
<section id="allies">
|
||||
<p class="eyebrow">{t('sections2.allies.eyebrow')}</p>
|
||||
<h2>{t('sections2.allies.title')}</h2>
|
||||
<p class="text-dim">{t('sections2.allies.intro')}</p>
|
||||
{
|
||||
groups.map(({ group, rows }) => (
|
||||
<>
|
||||
<h3 class="mt-6 mb-2 text-base">{t(`sections2.allies.${group}`)}</h3>
|
||||
<ul class="m-0 grid list-none gap-2 p-0 sm:grid-cols-2">
|
||||
{rows.map((a) => (
|
||||
<li id={a.id} class="m-0 rounded-dossier border border-rule bg-surface p-3">
|
||||
<p class="m-0 flex flex-wrap items-baseline gap-x-2">
|
||||
<Ext href={a.url} class="font-bold">
|
||||
{a.name}
|
||||
</Ext>
|
||||
<span class="font-mono text-xs text-dim">{a.displayUrl}</span>
|
||||
</p>
|
||||
<p class="mt-1 mb-0 text-sm leading-5 text-dim">{a.role}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
@@ -1,126 +0,0 @@
|
||||
---
|
||||
// PART 20 — the 14-step migration checklist (src/data/checklist.json +
|
||||
// per-locale prose). Real checkboxes, server-rendered: with JS off they
|
||||
// still toggle natively, they just don't persist. The script persists the
|
||||
// checked ids to localStorage ("ecc-checklist", a JSON array) and keeps the
|
||||
// progress line current; the server renders the 0-state.
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import { loadDataset, type ChecklistLevel } from '../lib/content'
|
||||
import { mdToHtml } from '../lib/md'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const items = loadDataset('checklist', locale)
|
||||
const template = t('sections2.checklist.progress') // "{done} / {total} …"
|
||||
const initialProgress = template.replace('{done}', '0').replace('{total}', String(items.length))
|
||||
|
||||
const LEVEL_CLASS: Record<ChecklistLevel, string> = {
|
||||
green: 'border-lvl1 text-lvl1',
|
||||
yellow: 'border-lvl2 text-lvl2',
|
||||
red: 'border-lvl3 text-lvl3',
|
||||
}
|
||||
---
|
||||
|
||||
<section id="checklist">
|
||||
<p class="eyebrow">{t('sections2.checklist.eyebrow')}</p>
|
||||
<h2>{t('sections2.checklist.title')}</h2>
|
||||
<p class="text-dim">{t('sections2.checklist.intro')}</p>
|
||||
<div data-checklist>
|
||||
<p
|
||||
data-checklist-progress
|
||||
data-template={template}
|
||||
aria-live="polite"
|
||||
class="font-mono text-sm font-bold text-accent"
|
||||
>
|
||||
{initialProgress}
|
||||
</p>
|
||||
<ul class="m-0 list-none p-0">
|
||||
{
|
||||
items.map((item) => (
|
||||
<li
|
||||
id={item.id}
|
||||
class="my-2 flex items-start gap-3 rounded-dossier border border-rule bg-surface p-3"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`check-${item.id}`}
|
||||
value={item.id}
|
||||
class="mt-1.5 size-4 shrink-0 cursor-pointer accent-accent"
|
||||
/>
|
||||
<label for={`check-${item.id}`} class="cursor-pointer text-sm leading-6">
|
||||
<span set:html={mdToHtml(item.body)} />
|
||||
<span
|
||||
class={`ms-1 rounded-dossier border px-1 py-0.5 align-middle font-mono text-[0.65rem] whitespace-nowrap ${LEVEL_CLASS[item.level]}`}
|
||||
>
|
||||
{t(`sections2.checklist.levels.${item.level}`)}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// Persistence is enhancement only: localStorage key "ecc-checklist" holds
|
||||
// the JSON array of checked ids. Change handling is delegated on document
|
||||
// (registers once, survives ClientRouter swaps); restore re-runs on every
|
||||
// astro:page-load since the swap replaces the DOM.
|
||||
const KEY = 'ecc-checklist'
|
||||
|
||||
const readSaved = (): string[] => {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(localStorage.getItem(KEY) ?? '[]')
|
||||
return Array.isArray(parsed) ? parsed.filter((x): x is string => typeof x === 'string') : []
|
||||
} catch {
|
||||
/* private mode or corrupted value: start unchecked */
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const boxes = (root: HTMLElement) => [
|
||||
...root.querySelectorAll<HTMLInputElement>('input[type="checkbox"]'),
|
||||
]
|
||||
|
||||
const updateProgress = (root: HTMLElement) => {
|
||||
const line = root.querySelector<HTMLElement>('[data-checklist-progress]')
|
||||
if (!line) return
|
||||
const all = boxes(root)
|
||||
const done = all.filter((b) => b.checked).length
|
||||
line.textContent = (line.dataset.template ?? '{done} / {total}')
|
||||
.replace('{done}', String(done))
|
||||
.replace('{total}', String(all.length))
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
const root = document.querySelector<HTMLElement>('[data-checklist]')
|
||||
if (!root) return
|
||||
const saved = new Set(readSaved())
|
||||
for (const box of boxes(root)) box.checked = saved.has(box.value)
|
||||
updateProgress(root)
|
||||
}
|
||||
|
||||
document.addEventListener('change', (e) => {
|
||||
const box = e.target as HTMLInputElement
|
||||
const root = box.closest?.('[data-checklist]') as HTMLElement | null
|
||||
if (!root || box.type !== 'checkbox') return
|
||||
const checked = boxes(root)
|
||||
.filter((b) => b.checked)
|
||||
.map((b) => b.value)
|
||||
try {
|
||||
localStorage.setItem(KEY, JSON.stringify(checked))
|
||||
} catch {
|
||||
/* private mode: ticks apply for this page only */
|
||||
}
|
||||
updateProgress(root)
|
||||
})
|
||||
|
||||
restore()
|
||||
document.addEventListener('astro:page-load', restore)
|
||||
</script>
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
// PART 19 — the open-source directory: 66 tools in 14 categories
|
||||
// (src/data/directory.json + per-locale one-line roles). Uniform compact
|
||||
// cards — deliberately NO featured/highlighted entry (that concept was
|
||||
// removed after PR #1; tests/unit/dist-links.test.ts asserts the smuggled
|
||||
// domains never come back). Category headings carry dir-<category> anchors.
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import { DIRECTORY_CATEGORIES, loadDataset } from '../lib/content'
|
||||
import { mdToHtml } from '../lib/md'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const entries = loadDataset('directory', locale)
|
||||
const byCategory = DIRECTORY_CATEGORIES.map((category) => ({
|
||||
category,
|
||||
rows: entries.filter((e) => e.category === category),
|
||||
}))
|
||||
---
|
||||
|
||||
<section id="directory">
|
||||
<p class="eyebrow">{t('sections2.directory.eyebrow')}</p>
|
||||
<h2>{t('sections2.directory.title')}</h2>
|
||||
<p class="text-dim">{t('sections2.directory.intro')}</p>
|
||||
{
|
||||
byCategory.map(({ category, rows }) => (
|
||||
<>
|
||||
<h3 id={`dir-${category}`} class="mt-6 mb-2 text-base">
|
||||
{t(`sections2.directory.categories.${category}`)}
|
||||
</h3>
|
||||
<ul class="m-0 grid list-none gap-2 p-0 sm:grid-cols-2">
|
||||
{rows.map((e) => (
|
||||
<li id={e.id} class="m-0 rounded-dossier border border-rule bg-surface p-3">
|
||||
<p class="m-0 flex items-baseline justify-between gap-2">
|
||||
<Ext href={e.url} class="font-bold">
|
||||
{e.name}
|
||||
</Ext>
|
||||
<span class="font-mono text-xs whitespace-nowrap text-dim">{e.license}</span>
|
||||
</p>
|
||||
<p class="mt-1 mb-0 text-sm leading-5 text-dim" set:html={mdToHtml(e.body)} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
// The ONLY way to render an external link. Enforces the site's link policy:
|
||||
// every external anchor carries rel="noopener noreferrer" — no exceptions,
|
||||
// no per-link rel overrides (that's how PR #1 smuggled follow backlinks).
|
||||
interface Props {
|
||||
href: string
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { href, class: className } = Astro.props
|
||||
if (!href.startsWith('https://')) {
|
||||
throw new Error(`Ext: external links must be https:// (got ${href})`)
|
||||
}
|
||||
---
|
||||
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" class={className}><slot /></a>
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
// A row of filter <button>s for the observatory. A button only flips a data
|
||||
// attribute on the section root (script in Observatory.astro) — the hiding
|
||||
// itself is pure CSS, so with JS off the bar is inert and all items stay
|
||||
// visible (the buttons are enhancement, never a gate on content).
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
group: string // e.g. "region" | "theme" → data-obs-<group> on the section
|
||||
label: string // localized group label
|
||||
allLabel: string // localized "All" (value "" = attribute removed)
|
||||
options: Option[]
|
||||
}
|
||||
|
||||
const { group, label, allLabel, options } = Astro.props
|
||||
|
||||
const btn =
|
||||
'cursor-pointer rounded-dossier border border-rule bg-surface-2 px-2 py-0.5 font-mono text-xs text-ink hover:border-accent aria-pressed:border-accent aria-pressed:bg-accent aria-pressed:text-on-accent'
|
||||
---
|
||||
|
||||
<div class="filterbar my-2 flex flex-wrap items-baseline gap-2" role="group" aria-label={label}>
|
||||
<span class="min-w-16 font-mono text-xs tracking-wide text-dim uppercase">{label}</span>
|
||||
<button type="button" data-obs-filter={group} data-value="" aria-pressed="true" class={btn}>
|
||||
{allLabel}
|
||||
</button>
|
||||
{
|
||||
options.map((o) => (
|
||||
<button
|
||||
type="button"
|
||||
data-obs-filter={group}
|
||||
data-value={o.value}
|
||||
aria-pressed="false"
|
||||
class={btn}
|
||||
>
|
||||
{o.label}
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
import { localePath, useT, type Locale } from '../i18n/config'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const resources = [
|
||||
{ href: 'https://www.privacyguides.org', label: 'Privacy Guides' },
|
||||
{ href: 'https://ssd.eff.org', label: 'EFF · Surveillance Self-Defense' },
|
||||
{ href: 'https://freedom.press', label: 'Freedom of the Press Foundation' },
|
||||
{ href: 'https://fightchatcontrol.eu', label: 'Fight Chat Control' },
|
||||
]
|
||||
|
||||
// Legislative sources — verified primary sources first (see docs/RESEARCH
|
||||
// notes in the repo history: EP press release + official roll-calls replace
|
||||
// the secondary blog sources the old version leaned on).
|
||||
const sources = [
|
||||
{
|
||||
href: 'https://www.europarl.europa.eu/news/en/press-room/20260706IPR46318/',
|
||||
key: 'ep0907',
|
||||
},
|
||||
{ href: 'https://howtheyvote.eu/votes/195775', key: 'rollcalls' },
|
||||
{
|
||||
href: 'https://www.consilium.europa.eu/en/press/press-releases/2026/07/02/council-moves-to-reinstate-interim-measure-to-combat-child-sexual-abuse-online/',
|
||||
key: 'council0207',
|
||||
},
|
||||
{
|
||||
href: 'https://www.europarl.europa.eu/news/en/press-room/20260325IPR39207/child-sexual-abuse-online-voluntary-detection-measures-will-not-be-extended',
|
||||
key: 'ep2603',
|
||||
},
|
||||
{
|
||||
href: 'https://www.edps.europa.eu/system/files/2026-02/26-02-16_opinion-extending-application-regulation-2021-1232_en.pdf',
|
||||
key: 'edps',
|
||||
},
|
||||
{ href: 'https://eur-lex.europa.eu/eli/reg/2021/1232/oj', key: 'reg1232' },
|
||||
{ href: 'https://fightchatcontrol.eu/chat-control-overview', key: 'ccOverview' },
|
||||
{ href: 'https://proton.me/blog/why-client-side-scanning-isnt-the-answer', key: 'protonCss' },
|
||||
{
|
||||
href: 'https://values.snap.com/news/joint-statement-eu-eprivacy-derogation',
|
||||
key: 'jointAppeal',
|
||||
},
|
||||
{ href: 'https://therecord.media/big-tech-vows-to-continue-csam-scanning', key: 'bigTechScan' },
|
||||
]
|
||||
|
||||
const act = [
|
||||
{ href: 'https://edri.org', label: 'European Digital Rights (EDRi)' },
|
||||
{ href: 'https://stopscanningme.eu', label: 'Stop Scanning Me (EDRi)' },
|
||||
{ href: 'https://fightchatcontrol.eu', label: 'Fight Chat Control' },
|
||||
{ href: 'https://signal.org/blog/', label: 'Signal · blog' },
|
||||
]
|
||||
|
||||
const offlineHref = `/exitchatcontrol-offline${locale === 'en' ? '' : `.${locale}`}.html`
|
||||
const localeRoot = `${localePath(locale)}/`.replace('//', '/')
|
||||
---
|
||||
|
||||
<footer class="mt-16 border-t border-rule-strong bg-surface">
|
||||
<div class="wrap grid gap-8 py-10 sm:grid-cols-3">
|
||||
<div>
|
||||
<h4 class="text-sm">{t('footer.resources')}</h4>
|
||||
<p class="text-sm leading-7">
|
||||
{
|
||||
resources.map((r) => (
|
||||
<>
|
||||
<Ext href={r.href}>{r.label}</Ext>
|
||||
<br />
|
||||
</>
|
||||
))
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm">{t('footer.sourcesLaw')}</h4>
|
||||
<p class="text-sm leading-7">
|
||||
{
|
||||
sources.map((s) => (
|
||||
<>
|
||||
<Ext href={s.href}>{t(`footer.links.${s.key}`)}</Ext>
|
||||
<br />
|
||||
</>
|
||||
))
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm">{t('footer.act')}</h4>
|
||||
<p class="text-sm leading-7">
|
||||
{
|
||||
act.map((a) => (
|
||||
<>
|
||||
<Ext href={a.href}>{a.label}</Ext>
|
||||
<br />
|
||||
</>
|
||||
))
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrap pb-4 text-center font-mono text-xs">
|
||||
<Ext href="https://github.com/Aurealibe/exitchatcontrol" class="ghlink">
|
||||
{t('footer.github')}
|
||||
</Ext>
|
||||
<span class="inline-block w-3"></span>
|
||||
<a href={offlineHref} download class="ghlink">⭳ {t('footer.download')}</a>
|
||||
</div>
|
||||
<div class="wrap pb-8">
|
||||
<p class="text-sm"><strong>{t('footer.proofline')}</strong></p>
|
||||
<small class="text-dim">
|
||||
{t('footer.disclaimer')}
|
||||
{t('footer.factsUpdated')}
|
||||
<a href={localeRoot} class="text-dim">exitchatcontrol.org</a>
|
||||
</small>
|
||||
<p class="mt-3 mb-0 text-center font-mono text-xs text-dim">
|
||||
by <Ext href="https://x.com/AureaLibe">Aurealibe</Ext>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
// Composition root: renders every migrated guide section for one locale.
|
||||
// Sections live in src/content/sections/<locale>/NN-<slug>.mdx; each keeps
|
||||
// its LEGACY anchor id on the <section> wrapper so old deep links resolve.
|
||||
// ToolCard is injected into the MDX scope via the components prop.
|
||||
import { getCollection, render } from 'astro:content'
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import Allies from './Allies.astro'
|
||||
import Checklist from './Checklist.astro'
|
||||
import Directory from './Directory.astro'
|
||||
import Hero from './Hero.astro'
|
||||
import Manifesto from './Manifesto.astro'
|
||||
import Observatory from './Observatory.astro'
|
||||
import ShareRow from './ShareRow.astro'
|
||||
import StatusBanner from './StatusBanner.astro'
|
||||
import Timeline from './Timeline.astro'
|
||||
import ToolCard from './ToolCard.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const entries = await getCollection('sections', ({ id }) => id.startsWith(`${locale}/`))
|
||||
entries.sort((a, b) => a.data.part - b.data.part || a.data.order - b.data.order)
|
||||
const sections = await Promise.all(
|
||||
entries.map(async (entry) => ({ data: entry.data, Content: (await render(entry)).Content })),
|
||||
)
|
||||
|
||||
// The dataset-driven sections appended after the migrated guide, in reading
|
||||
// order — also listed in the TOC below (titles from the same i18n keys).
|
||||
const dataSections = ['precedents', 'observatory', 'allies', 'directory', 'checklist'].map(
|
||||
(id) => ({ id, title: t(`sections2.${id}.title`) }),
|
||||
)
|
||||
|
||||
// Sequential part numbering, 00…27 — mirrors the PART NN headers inside
|
||||
// the sections (renumbered the same way).
|
||||
const tocEntries = [
|
||||
...sections.map(({ data }) => ({ id: data.id, title: data.title })),
|
||||
...dataSections,
|
||||
{ id: 'manifesto', title: t('manifesto.label') },
|
||||
].map((s, i) => ({ ...s, prefix: String(i).padStart(2, '0') }))
|
||||
---
|
||||
|
||||
<Hero locale={locale} />
|
||||
<StatusBanner locale={locale} />
|
||||
<main id="content" class="mx-auto max-w-3xl px-4 py-8">
|
||||
<nav
|
||||
id="toc"
|
||||
aria-label={t('nav.toc')}
|
||||
class="my-8 rounded-dossier border border-rule bg-surface p-5"
|
||||
>
|
||||
<p class="eyebrow mt-0 mb-3">{t('nav.toc')}</p>
|
||||
<ul class="m-0 list-none gap-6 p-0 text-sm leading-7 sm:columns-2">
|
||||
{
|
||||
tocEntries.map((s) => (
|
||||
<li class="m-0">
|
||||
<a href={`#${s.id}`} class="text-ink no-underline hover:text-accent">
|
||||
<span class="toc-p" aria-hidden="true">
|
||||
{s.prefix}
|
||||
</span>
|
||||
{s.title}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
{
|
||||
sections.map(({ data, Content }) => (
|
||||
<section id={data.id}>
|
||||
<Content components={{ ToolCard }} />
|
||||
</section>
|
||||
))
|
||||
}
|
||||
<Timeline locale={locale} />
|
||||
<Observatory locale={locale} />
|
||||
<Allies locale={locale} />
|
||||
<Directory locale={locale} />
|
||||
<Checklist locale={locale} />
|
||||
<Manifesto locale={locale} />
|
||||
<ShareRow locale={locale} />
|
||||
</main>
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
// The stamped-dossier hero, restored from the legacy site: rotated warning
|
||||
// stamp, mono eyebrow, oversized typewriter title with blinking cursor,
|
||||
// serif lede and the three reader-profile chips (their colors legend the
|
||||
// 🟢🟡🔴 levels used across the guide).
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
---
|
||||
|
||||
<header class="hero">
|
||||
<div class="hero-inner">
|
||||
<span class="stamp">{t('hero.stamp')}</span>
|
||||
<p class="eyebrow">{t('hero.eyebrow')}</p>
|
||||
<h1 id="top">EXIT CHAT CONTROL<span class="cursor">_</span></h1>
|
||||
<p class="lede">{t('hero.lede')}</p>
|
||||
<div class="hero-meta">
|
||||
<span class="tag p1">{t('hero.p1')}</span>
|
||||
<span class="tag p2">{t('hero.p2')}</span>
|
||||
<span class="tag p3">{t('hero.p3')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
// Brand icon from the build-time-embedded simple-icons set (CC0).
|
||||
// Unknown slug → nothing renders; callers keep their monogram fallback.
|
||||
import { BRAND_ICONS } from '../icons.generated'
|
||||
|
||||
interface Props {
|
||||
slug: string
|
||||
size?: number
|
||||
class?: string
|
||||
}
|
||||
|
||||
const { slug, size = 18, class: className } = Astro.props
|
||||
const path = BRAND_ICONS[slug]
|
||||
---
|
||||
|
||||
{
|
||||
path && (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
class={className}
|
||||
>
|
||||
<path d={path} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
// The closing manifesto, the guide's last word before the footer (owner's
|
||||
// own copy). Inverted paper panel, typewriter mono, amber emphasis, the
|
||||
// final phrase highlighted. p3 carries the fightchatcontrol.eu link via a
|
||||
// {link} placeholder so word order stays free in every language.
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
const [p3before, p3after] = t('manifesto.p3').split('{link}')
|
||||
---
|
||||
|
||||
<section id="manifesto" class="manifesto">
|
||||
<p class="eyebrow">{t('manifesto.label')}</p>
|
||||
<p>{t('manifesto.p1')}</p>
|
||||
<p><Fragment set:html={t('manifesto.p2')} /></p>
|
||||
<p>
|
||||
{p3before}<Ext href="https://fightchatcontrol.eu">fightchatcontrol.eu</Ext>{p3after}
|
||||
</p>
|
||||
<p>{t('manifesto.p4')}</p>
|
||||
</section>
|
||||
@@ -1,138 +0,0 @@
|
||||
---
|
||||
// PART 17 — the Big Brother observatory: 35 dated, sourced drift items
|
||||
// (src/data/observatory.json + per-locale prose), filterable by region group
|
||||
// and by theme. The filtering is attribute-driven CSS (see <style> below):
|
||||
// the buttons only set data-obs-region / data-obs-theme on the section root,
|
||||
// so the no-JS state is simply "everything visible".
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import { loadDataset, THEMES, type Status } from '../lib/content'
|
||||
import { mdToHtml } from '../lib/md'
|
||||
import Ext from './Ext.astro'
|
||||
import FilterBar from './FilterBar.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const items = [...loadDataset('observatory', locale)].sort((a, b) => a.date.localeCompare(b.date))
|
||||
|
||||
// The UI groups the 10 raw regions into the 3 chips the dataset guarantees
|
||||
// are never empty (see tests/unit/content.test.ts): EU, France, World.
|
||||
const REGION_GROUPS = ['eu', 'fr', 'world'] as const
|
||||
type RegionGroup = (typeof REGION_GROUPS)[number]
|
||||
const regionGroup = (region: string): RegionGroup =>
|
||||
region === 'eu' ? 'eu' : region === 'fr' ? 'fr' : 'world'
|
||||
|
||||
const STATUS_CLASS: Record<Status, string> = {
|
||||
en_vigueur: 'border-warn text-warn',
|
||||
adopte: 'border-warn text-warn',
|
||||
negociation: 'border-lvl2 text-lvl2',
|
||||
propose: 'border-info text-info',
|
||||
rejete: 'border-ok text-ok',
|
||||
suspendu: 'border-ok text-ok',
|
||||
revele: 'border-info text-info',
|
||||
}
|
||||
---
|
||||
|
||||
<section id="observatory">
|
||||
<p class="eyebrow">{t('sections2.observatory.eyebrow')}</p>
|
||||
<h2>{t('sections2.observatory.title')}</h2>
|
||||
<p class="text-dim">{t('sections2.observatory.intro')}</p>
|
||||
<FilterBar
|
||||
group="region"
|
||||
label={t('sections2.observatory.filterRegion')}
|
||||
allLabel={t('sections2.observatory.all')}
|
||||
options={REGION_GROUPS.map((g) => ({
|
||||
value: g,
|
||||
label: t(`sections2.observatory.regions.${g}`),
|
||||
}))}
|
||||
/>
|
||||
<FilterBar
|
||||
group="theme"
|
||||
label={t('sections2.observatory.filterTheme')}
|
||||
allLabel={t('sections2.observatory.all')}
|
||||
options={THEMES.map((th) => ({ value: th, label: t(`sections2.observatory.themes.${th}`) }))}
|
||||
/>
|
||||
<div class="mt-4">
|
||||
{
|
||||
items.map((ev) => (
|
||||
<article
|
||||
id={ev.id}
|
||||
class="obs-item my-5"
|
||||
data-region={regionGroup(ev.region)}
|
||||
data-themes={ev.themes.join(' ')}
|
||||
>
|
||||
<p class="m-0 flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||
<span class="font-mono text-sm font-bold">{ev.date}</span>
|
||||
<span class="rounded-dossier border border-rule px-1.5 py-0.5 font-mono text-xs text-dim">
|
||||
{t(`sections2.observatory.regions.${regionGroup(ev.region)}`)}
|
||||
</span>
|
||||
{ev.themes.map((th) => (
|
||||
<span class="rounded-dossier border border-rule px-1.5 py-0.5 font-mono text-xs text-dim">
|
||||
{t(`sections2.observatory.themes.${th}`)}
|
||||
</span>
|
||||
))}
|
||||
<span
|
||||
class={`rounded-dossier border px-1.5 py-0.5 font-mono text-xs tracking-wide uppercase ${STATUS_CLASS[ev.status]}`}
|
||||
>
|
||||
{t(`sections2.observatory.statuses.${ev.status}`)}
|
||||
</span>
|
||||
</p>
|
||||
<h3 class="mt-1 mb-0 text-base">{ev.title}</h3>
|
||||
<p class="my-1 text-sm leading-6" set:html={mdToHtml(ev.body)} />
|
||||
<p class="mt-1 mb-0 font-mono text-xs">
|
||||
<Ext href={ev.src.href} class="text-dim">
|
||||
{ev.src.label} →
|
||||
</Ext>
|
||||
</p>
|
||||
</article>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Attribute-driven filtering: one rule per chip value, mirroring
|
||||
REGION_GROUPS and THEMES above. The buttons only flip the section's
|
||||
data attributes — no attribute, nothing hidden (the no-JS state). */
|
||||
section[data-obs-region='eu'] .obs-item:not([data-region='eu']),
|
||||
section[data-obs-region='fr'] .obs-item:not([data-region='fr']),
|
||||
section[data-obs-region='world'] .obs-item:not([data-region='world']),
|
||||
section[data-obs-theme='messaging'] .obs-item:not([data-themes~='messaging']),
|
||||
section[data-obs-theme='identity'] .obs-item:not([data-themes~='identity']),
|
||||
section[data-obs-theme='money'] .obs-item:not([data-themes~='money']),
|
||||
section[data-obs-theme='media'] .obs-item:not([data-themes~='media']),
|
||||
section[data-obs-theme='aibio'] .obs-item:not([data-themes~='aibio']) {
|
||||
display: none;
|
||||
}
|
||||
/* the printed dossier is always complete, whatever filters are active */
|
||||
@media print {
|
||||
.obs-item {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Delegated once per page lifecycle (survives ClientRouter swaps): a
|
||||
// filter button sets/removes the section's data attribute and mirrors
|
||||
// the pressed state on its own group's buttons.
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-obs-filter]')
|
||||
if (!btn) return
|
||||
const root = btn.closest<HTMLElement>('#observatory')
|
||||
if (!root) return
|
||||
const group = btn.dataset.obsFilter ?? ''
|
||||
const value = btn.dataset.value ?? ''
|
||||
if (value) root.setAttribute(`data-obs-${group}`, value)
|
||||
else root.removeAttribute(`data-obs-${group}`)
|
||||
for (const other of root.querySelectorAll(`[data-obs-filter="${group}"]`)) {
|
||||
other.setAttribute('aria-pressed', String(other === btn))
|
||||
}
|
||||
})
|
||||
|
||||
export {}
|
||||
</script>
|
||||
@@ -1,470 +0,0 @@
|
||||
---
|
||||
// Censorship-resistance quiz — a progressive-enhancement scorer. EVERYTHING
|
||||
// the reader needs is server-rendered: the twelve questions (native radios,
|
||||
// selectable with JS off) and the full scoring key (what each band means +
|
||||
// where to improve, with deep links into the guide). The client script only
|
||||
// computes the live score and reveals the result panel; with JS off the
|
||||
// static key still teaches the whole thing. No third-party request, no inline
|
||||
// styles (bar widths come from a CSS custom property set via the CSSOM, which
|
||||
// style-src 'self' allows), no inline handlers (delegated on document).
|
||||
import { localePath, useT, type Locale } from '../i18n/config'
|
||||
import { loadQuiz } from '../lib/content'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
const { bands, questions } = loadQuiz(locale)
|
||||
|
||||
const localeRoot = `${localePath(locale)}/`.replace('//', '/')
|
||||
const guideHref = (anchor: string) => `${localeRoot}#${anchor}`
|
||||
const total = questions.length
|
||||
const progress0 = t('quiz.progress').replace('{done}', '0').replace('{total}', String(total))
|
||||
---
|
||||
|
||||
<main id="content" class="mx-auto max-w-3xl px-4 py-8">
|
||||
<header class="my-8">
|
||||
<p class="eyebrow mt-0">{t('quiz.eyebrow')}</p>
|
||||
<h1 class="my-2 text-4xl uppercase sm:text-5xl">{t('quiz.title')}</h1>
|
||||
<p class="text-dim max-w-[60ch]">{t('quiz.lede')}</p>
|
||||
<p class="text-dim font-mono text-xs">{t('quiz.privacyNote')}</p>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<a
|
||||
href="#quiz-form"
|
||||
class="rounded-dossier border border-accent bg-accent px-3 py-1.5 font-mono text-xs text-on-accent no-underline"
|
||||
>
|
||||
{t('quiz.start')}
|
||||
</a>
|
||||
<a
|
||||
href={localeRoot}
|
||||
class="rounded-dossier border border-rule bg-surface px-3 py-1.5 font-mono text-xs text-ink no-underline hover:border-accent"
|
||||
>
|
||||
{t('quiz.readGuide')}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div data-quiz-progress class="sticky top-12 z-30 bg-bg/95 py-2 backdrop-blur-sm" hidden>
|
||||
<p
|
||||
data-quiz-progress-text
|
||||
data-tpl={t('quiz.progress')}
|
||||
class="eyebrow mt-0 mb-1"
|
||||
aria-live="polite"
|
||||
>
|
||||
{progress0}
|
||||
</p>
|
||||
<div class="quiz-progress" aria-hidden="true"><i class="quiz-progress__fill"></i></div>
|
||||
</div>
|
||||
|
||||
<form id="quiz-form" data-quiz aria-describedby="quiz-note">
|
||||
<p id="quiz-note" class="sr-only">{t('quiz.privacyNote')}</p>
|
||||
<ol class="m-0 list-none p-0">
|
||||
{
|
||||
questions.map((question, i) => (
|
||||
<li class="my-6">
|
||||
<fieldset
|
||||
data-quiz-question
|
||||
data-cat={question.id}
|
||||
class="rounded-dossier border border-rule bg-surface m-0 p-4"
|
||||
>
|
||||
<legend class="px-1 font-mono text-base font-bold">
|
||||
<span class="text-accent">{String(i + 1).padStart(2, '0')}</span> {question.prompt}
|
||||
</legend>
|
||||
<div class="mt-3 grid gap-2 sm:grid-cols-2">
|
||||
{question.options.map((option) => (
|
||||
<label class="rounded-dossier border border-rule bg-surface-2 has-[:checked]:border-accent has-[:checked]:bg-surface flex cursor-pointer flex-col gap-1 p-3">
|
||||
<span class="flex items-start gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name={question.id}
|
||||
value={option.points}
|
||||
class="mt-1 size-4 shrink-0 cursor-pointer accent-accent"
|
||||
/>
|
||||
<span class="font-mono text-sm font-bold">{option.label}</span>
|
||||
</span>
|
||||
<span class="text-dim ms-6 text-sm">{option.hint}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</form>
|
||||
|
||||
<section
|
||||
data-quiz-result
|
||||
data-share-tpl={t('quiz.shareText')}
|
||||
tabindex="-1"
|
||||
aria-live="polite"
|
||||
class="rounded-dossier border border-rule border-l-4 border-l-accent bg-surface my-8 p-5"
|
||||
hidden
|
||||
>
|
||||
<p class="eyebrow mt-0 mb-1">{t('quiz.resultEyebrow')}</p>
|
||||
<p class="font-mono text-5xl font-bold">
|
||||
<span data-quiz-score>0</span><span class="text-dim text-2xl"> {t('quiz.outOf')}</span>
|
||||
</p>
|
||||
<p data-quiz-band class="text-accent font-mono text-sm font-bold tracking-widest uppercase"></p>
|
||||
<p data-quiz-summary class="text-dim"></p>
|
||||
|
||||
<h3 class="mt-6 mb-2 font-mono text-sm uppercase">{t('quiz.barsHeading')}</h3>
|
||||
<div data-quiz-bars class="grid gap-2"></div>
|
||||
|
||||
<h3 class="mt-6 mb-2 font-mono text-sm uppercase">{t('quiz.recHeading')}</h3>
|
||||
<div data-quiz-recs class="grid gap-2 sm:grid-cols-3"></div>
|
||||
|
||||
<div class="mt-5 flex flex-wrap gap-2 border-t border-rule pt-4">
|
||||
<button
|
||||
type="button"
|
||||
data-quiz-share="copy"
|
||||
data-copied={t('quiz.copied')}
|
||||
class="rounded-dossier border border-rule bg-surface-2 text-ink cursor-pointer px-3 py-1.5 font-mono text-xs hover:border-accent"
|
||||
>
|
||||
{t('quiz.copy')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-quiz-share="native"
|
||||
class="rounded-dossier border border-rule bg-surface-2 text-ink cursor-pointer px-3 py-1.5 font-mono text-xs hover:border-accent"
|
||||
hidden
|
||||
>
|
||||
{t('quiz.share')}
|
||||
</button>
|
||||
<Ext
|
||||
href="https://x.com/intent/tweet"
|
||||
class="quiz-x-share rounded-dossier border border-rule bg-surface-2 text-ink px-3 py-1.5 font-mono text-xs no-underline hover:border-accent"
|
||||
>
|
||||
{t('quiz.shareX')}
|
||||
</Ext>
|
||||
<button
|
||||
type="button"
|
||||
data-quiz-restart
|
||||
class="rounded-dossier border border-rule bg-surface text-dim ms-auto cursor-pointer px-3 py-1.5 font-mono text-xs hover:border-accent"
|
||||
>
|
||||
{t('quiz.restart')}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="quiz-key" aria-labelledby="quiz-key-bands" class="my-10">
|
||||
<h2 id="quiz-key-bands" class="text-2xl uppercase">{t('quiz.bandsHeading')}</h2>
|
||||
<ul data-quiz-band-key class="m-0 grid list-none gap-2 p-0">
|
||||
{
|
||||
bands.map((band) => (
|
||||
<li
|
||||
data-band-id={band.id}
|
||||
data-band-min={band.min}
|
||||
data-band-max={band.max}
|
||||
class="rounded-dossier border border-rule bg-surface p-3"
|
||||
>
|
||||
<span class="flex items-baseline gap-2">
|
||||
<strong data-band-title class="font-mono text-sm uppercase">
|
||||
{band.title}
|
||||
</strong>
|
||||
<span class="text-dim font-mono text-xs">
|
||||
{band.min}–{band.max}
|
||||
</span>
|
||||
</span>
|
||||
<p data-band-summary class="text-dim my-1 text-sm">
|
||||
{band.summary}
|
||||
</p>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<h2 class="mt-8 text-2xl uppercase">{t('quiz.categoriesHeading')}</h2>
|
||||
<ul data-quiz-cat-key class="m-0 grid list-none gap-2 p-0 sm:grid-cols-2">
|
||||
{
|
||||
questions.map((question) => (
|
||||
<li data-cat={question.id} class="rounded-dossier border border-rule bg-surface p-3">
|
||||
<strong data-cat-label class="font-mono text-sm">
|
||||
{question.label}
|
||||
</strong>
|
||||
<p data-cat-rec class="text-dim my-1 text-sm">
|
||||
{question.rec}
|
||||
</p>
|
||||
<a data-cat-link href={guideHref(question.anchor)} class="font-mono text-xs">
|
||||
{t('quiz.openGuide')}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ── Quiz enhancement ──────────────────────────────────────────────────
|
||||
// Delegated on document (survives ClientRouter swaps), re-init on
|
||||
// astro:page-load. Answers persist in localStorage 'ecc-quiz'.
|
||||
const KEY = 'ecc-quiz'
|
||||
type Answers = Record<string, number>
|
||||
|
||||
const qa = <T extends Element>(sel: string, root: ParentNode = document): T[] =>
|
||||
Array.from(root.querySelectorAll<T>(sel))
|
||||
const one = <T extends Element>(sel: string, root: ParentNode = document): T | null =>
|
||||
root.querySelector<T>(sel)
|
||||
|
||||
const form = () => one<HTMLFormElement>('[data-quiz]')
|
||||
|
||||
const readSaved = (): Answers => {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(localStorage.getItem(KEY) ?? '{}')
|
||||
const out: Answers = {}
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
||||
if (typeof v === 'number') out[k] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const save = (answers: Answers) => {
|
||||
try {
|
||||
localStorage.setItem(KEY, JSON.stringify(answers))
|
||||
} catch {
|
||||
/* private mode: not persisted, quiz still works for this visit */
|
||||
}
|
||||
}
|
||||
|
||||
type Cat = { cat: string; points: number | null; max: number }
|
||||
|
||||
const scan = (root: HTMLFormElement): { total: number; answered: number; cats: Cat[] } => {
|
||||
const cats: Cat[] = []
|
||||
let answered = 0
|
||||
for (const fs of qa<HTMLFieldSetElement>('[data-quiz-question]', root)) {
|
||||
const radios = qa<HTMLInputElement>('input[type="radio"]', fs)
|
||||
const max = Math.max(0, ...radios.map((r) => Number(r.value)))
|
||||
const checked = radios.find((r) => r.checked)
|
||||
if (checked) answered += 1
|
||||
cats.push({ cat: fs.dataset.cat ?? '', points: checked ? Number(checked.value) : null, max })
|
||||
}
|
||||
return { total: cats.length, answered, cats }
|
||||
}
|
||||
|
||||
const bandFor = (score: number): { title: string; summary: string } => {
|
||||
for (const li of qa<HTMLElement>('[data-quiz-band-key] [data-band-id]')) {
|
||||
const min = Number(li.dataset.bandMin)
|
||||
const max = Number(li.dataset.bandMax)
|
||||
if (score >= min && score <= max) {
|
||||
return {
|
||||
title: one<HTMLElement>('[data-band-title]', li)?.textContent ?? '',
|
||||
summary: one<HTMLElement>('[data-band-summary]', li)?.textContent ?? '',
|
||||
}
|
||||
}
|
||||
}
|
||||
return { title: '', summary: '' }
|
||||
}
|
||||
|
||||
const catNode = (cat: string) => one<HTMLElement>(`[data-quiz-cat-key] [data-cat="${cat}"]`)
|
||||
const catLabel = (cat: string) =>
|
||||
one<HTMLElement>('[data-cat-label]', catNode(cat) ?? undefined)?.textContent ?? cat
|
||||
|
||||
const setBar = (fill: HTMLElement, ratio: number) => {
|
||||
fill.style.setProperty('--quiz-bar', `${Math.round(ratio * 100)}%`)
|
||||
}
|
||||
|
||||
const buildBars = (cats: Cat[]) => {
|
||||
const host = one<HTMLElement>('[data-quiz-bars]')
|
||||
if (!host) return
|
||||
host.textContent = ''
|
||||
for (const c of cats) {
|
||||
const ratio = c.max > 0 && c.points != null ? c.points / c.max : 0
|
||||
const row = document.createElement('div')
|
||||
row.className = 'quiz-bar-row'
|
||||
const label = document.createElement('span')
|
||||
label.className = 'quiz-bar-row__label font-mono text-xs text-dim uppercase'
|
||||
label.textContent = catLabel(c.cat)
|
||||
const track = document.createElement('div')
|
||||
track.className = 'quiz-bar'
|
||||
const fill = document.createElement('i')
|
||||
fill.className = 'quiz-bar__fill'
|
||||
setBar(fill, ratio)
|
||||
track.appendChild(fill)
|
||||
const val = document.createElement('strong')
|
||||
val.className = 'font-mono text-xs'
|
||||
val.textContent = `${c.points ?? 0}/${c.max}`
|
||||
row.append(label, track, val)
|
||||
host.appendChild(row)
|
||||
}
|
||||
}
|
||||
|
||||
const buildRecs = (cats: Cat[]) => {
|
||||
const host = one<HTMLElement>('[data-quiz-recs]')
|
||||
if (!host) return
|
||||
host.textContent = ''
|
||||
const weakest = [...cats]
|
||||
.filter((c) => c.points != null)
|
||||
.sort((a, b) => a.points! / a.max - b.points! / b.max)
|
||||
.slice(0, 3)
|
||||
for (const c of weakest) {
|
||||
const node = catNode(c.cat)
|
||||
const card = document.createElement('div')
|
||||
card.className =
|
||||
'rounded-dossier border border-rule border-l-2 border-l-accent bg-surface p-3'
|
||||
const label = document.createElement('strong')
|
||||
label.className = 'font-mono text-sm'
|
||||
label.textContent = catLabel(c.cat)
|
||||
const rec = document.createElement('p')
|
||||
rec.className = 'text-dim my-1 text-sm'
|
||||
rec.textContent = one<HTMLElement>('[data-cat-rec]', node ?? undefined)?.textContent ?? ''
|
||||
const link = document.createElement('a')
|
||||
link.className = 'font-mono text-xs'
|
||||
const src = one<HTMLAnchorElement>('[data-cat-link]', node ?? undefined)
|
||||
link.href = src?.getAttribute('href') ?? '#'
|
||||
link.textContent = src?.textContent ?? ''
|
||||
card.append(label, rec, link)
|
||||
host.appendChild(card)
|
||||
}
|
||||
}
|
||||
|
||||
const shareUrl = () => location.href.replace(/[?#].*$/, '')
|
||||
|
||||
const shareMessage = (score: number, band: string): string => {
|
||||
const panel = one<HTMLElement>('[data-quiz-result]')
|
||||
const tpl = panel?.dataset.shareTpl ?? '{score}/100 — {band}'
|
||||
return tpl.replace('{score}', String(score)).replace('{band}', band)
|
||||
}
|
||||
|
||||
const showResult = (cats: Cat[]) => {
|
||||
const totalPoints = cats.reduce((s, c) => s + (c.points ?? 0), 0)
|
||||
const maxPoints = cats.reduce((s, c) => s + c.max, 0)
|
||||
const score = maxPoints > 0 ? Math.round((totalPoints / maxPoints) * 100) : 0
|
||||
const band = bandFor(score)
|
||||
|
||||
const scoreEl = one<HTMLElement>('[data-quiz-score]')
|
||||
if (scoreEl) scoreEl.textContent = String(score)
|
||||
const bandEl = one<HTMLElement>('[data-quiz-band]')
|
||||
if (bandEl) bandEl.textContent = band.title
|
||||
const summaryEl = one<HTMLElement>('[data-quiz-summary]')
|
||||
if (summaryEl) summaryEl.textContent = band.summary
|
||||
|
||||
buildBars(cats)
|
||||
buildRecs(cats)
|
||||
|
||||
const msg = shareMessage(score, band.title)
|
||||
const x = one<HTMLAnchorElement>('.quiz-x-share')
|
||||
if (x) x.href = `https://x.com/intent/tweet?text=${encodeURIComponent(`${msg} ${shareUrl()}`)}`
|
||||
|
||||
const panel = one<HTMLElement>('[data-quiz-result]')
|
||||
if (panel && panel.hidden) {
|
||||
panel.hidden = false
|
||||
panel.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
panel.focus({ preventScroll: true })
|
||||
}
|
||||
}
|
||||
|
||||
const hideResult = () => {
|
||||
const panel = one<HTMLElement>('[data-quiz-result]')
|
||||
if (panel) panel.hidden = true
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
const root = form()
|
||||
if (!root) return
|
||||
const { total, answered, cats } = scan(root)
|
||||
|
||||
const block = one<HTMLElement>('[data-quiz-progress]')
|
||||
if (block) block.hidden = false
|
||||
const fill = one<HTMLElement>('[data-quiz-progress] .quiz-progress__fill')
|
||||
if (fill) fill.style.setProperty('--quiz-progress', `${total ? (answered / total) * 100 : 0}%`)
|
||||
const text = one<HTMLElement>('[data-quiz-progress-text]')
|
||||
if (text)
|
||||
text.textContent = (text.dataset.tpl ?? '{done} / {total}')
|
||||
.replace('{done}', String(answered))
|
||||
.replace('{total}', String(total))
|
||||
|
||||
const answers: Answers = {}
|
||||
for (const c of cats) if (c.points != null) answers[c.cat] = c.points
|
||||
save(answers)
|
||||
|
||||
if (answered === total && total > 0) showResult(cats)
|
||||
else hideResult()
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
const root = form()
|
||||
if (!root) return
|
||||
const native = one<HTMLButtonElement>('[data-quiz-share="native"]')
|
||||
if (native) native.hidden = typeof navigator.share !== 'function'
|
||||
|
||||
const saved = readSaved()
|
||||
for (const fs of qa<HTMLFieldSetElement>('[data-quiz-question]', root)) {
|
||||
const cat = fs.dataset.cat ?? ''
|
||||
if (!(cat in saved)) continue
|
||||
for (const radio of qa<HTMLInputElement>('input[type="radio"]', fs)) {
|
||||
if (Number(radio.value) === saved[cat]) radio.checked = true
|
||||
}
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
const restart = () => {
|
||||
const root = form()
|
||||
if (!root) return
|
||||
for (const radio of qa<HTMLInputElement>('input[type="radio"]', root)) radio.checked = false
|
||||
try {
|
||||
localStorage.removeItem(KEY)
|
||||
} catch {
|
||||
/* nothing to clear */
|
||||
}
|
||||
hideResult()
|
||||
update()
|
||||
root.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
|
||||
document.addEventListener('change', (e) => {
|
||||
const target = e.target as HTMLElement
|
||||
if (
|
||||
target instanceof HTMLInputElement &&
|
||||
target.type === 'radio' &&
|
||||
target.closest('[data-quiz]')
|
||||
)
|
||||
update()
|
||||
})
|
||||
|
||||
document.addEventListener('click', async (e) => {
|
||||
const target = e.target as HTMLElement
|
||||
if (target.closest('[data-quiz-restart]')) {
|
||||
restart()
|
||||
return
|
||||
}
|
||||
const btn = target.closest<HTMLButtonElement>('[data-quiz-share]')
|
||||
if (!btn) return
|
||||
const action = btn.dataset.quizShare
|
||||
const score = one<HTMLElement>('[data-quiz-score]')?.textContent ?? ''
|
||||
const band = one<HTMLElement>('[data-quiz-band]')?.textContent ?? ''
|
||||
const message = shareMessage(Number(score), band)
|
||||
if (action === 'native') {
|
||||
try {
|
||||
await navigator.share({ title: document.title, text: message, url: shareUrl() })
|
||||
} catch {
|
||||
/* dismissed or unsupported */
|
||||
}
|
||||
return
|
||||
}
|
||||
if (action === 'copy') {
|
||||
try {
|
||||
await navigator.clipboard.writeText(`${message} ${shareUrl()}`)
|
||||
if (!btn.dataset.label) btn.dataset.label = btn.textContent ?? ''
|
||||
btn.textContent = btn.dataset.copied ?? btn.dataset.label
|
||||
window.setTimeout(() => {
|
||||
btn.textContent = btn.dataset.label ?? ''
|
||||
}, 2000)
|
||||
} catch {
|
||||
/* clipboard unavailable */
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
restore()
|
||||
document.addEventListener('astro:page-load', restore)
|
||||
</script>
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
// Share / copy-link / print row at the end of the guide. Pure enhancement:
|
||||
// the whole row is server-rendered hidden and revealed by script (none of
|
||||
// the three actions can work without JS), and print.css hides .share-row on
|
||||
// paper. The share button additionally stays hidden when the Web Share API
|
||||
// is unsupported.
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const btn =
|
||||
'cursor-pointer rounded-dossier border border-rule bg-surface-2 px-3 py-1.5 font-mono text-xs text-ink hover:border-accent'
|
||||
---
|
||||
|
||||
<div
|
||||
class="share-row mt-12 border-t border-rule pt-5"
|
||||
data-share-row
|
||||
aria-label={t('share.label')}
|
||||
hidden
|
||||
>
|
||||
<p class="eyebrow mt-0 mb-3">{t('share.label')}</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button type="button" data-share-action="share" class={btn} hidden>
|
||||
{t('share.share')}
|
||||
</button>
|
||||
<button type="button" data-share-action="copy" data-copied={t('share.copied')} class={btn}>
|
||||
{t('share.copy')}
|
||||
</button>
|
||||
<button type="button" data-share-action="print" class={btn}>
|
||||
{t('share.print')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Reveal on load (and after every ClientRouter swap); actions delegated
|
||||
// once on document.
|
||||
const reveal = () => {
|
||||
for (const row of document.querySelectorAll<HTMLElement>('[data-share-row]')) {
|
||||
row.hidden = false
|
||||
const share = row.querySelector<HTMLElement>('[data-share-action="share"]')
|
||||
if (share) share.hidden = typeof navigator.share !== 'function'
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', async (e) => {
|
||||
const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-share-action]')
|
||||
if (!btn) return
|
||||
const url = location.href.replace(/#.*$/, '')
|
||||
const action = btn.dataset.shareAction
|
||||
if (action === 'print') {
|
||||
window.print()
|
||||
return
|
||||
}
|
||||
if (action === 'share') {
|
||||
try {
|
||||
await navigator.share({ title: document.title, url })
|
||||
} catch {
|
||||
/* dismissed or unsupported: nothing to clean up */
|
||||
}
|
||||
return
|
||||
}
|
||||
if (action === 'copy') {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url)
|
||||
if (!btn.dataset.label) btn.dataset.label = btn.textContent ?? ''
|
||||
btn.textContent = btn.dataset.copied ?? btn.dataset.label
|
||||
window.setTimeout(() => {
|
||||
btn.textContent = btn.dataset.label ?? ''
|
||||
}, 2000)
|
||||
} catch {
|
||||
/* clipboard unavailable (permissions, http): leave the label as-is */
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
reveal()
|
||||
document.addEventListener('astro:page-load', reveal)
|
||||
</script>
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
// The legislative status banner — the legacy "Dernière minute" band look
|
||||
// (full-width strip, pulsing red dot, mono warn label), carrying the
|
||||
// VERIFIED status: every claim is primary-sourced (EP press release
|
||||
// 20260706IPR46318 + official roll-calls). When the Council answers
|
||||
// (deadline ≈ 9 Oct 2026), update THIS component's i18n keys (status.*).
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
---
|
||||
|
||||
<aside aria-label={t('status.label')} class="status">
|
||||
<div class="wrap status-inner">
|
||||
<span class="dot" aria-hidden="true"></span>
|
||||
<div>
|
||||
<b>{t('status.label')}</b>
|
||||
<p class="status-headline">{t('status.headline')}</p>
|
||||
<p>{t('status.body')}</p>
|
||||
<p class="status-cta">{t('status.cta')}</p>
|
||||
<p class="status-sources">
|
||||
<Ext href="https://www.europarl.europa.eu/news/en/press-room/20260706IPR46318/">
|
||||
{t('status.sourceEp')}
|
||||
</Ext>
|
||||
·
|
||||
<Ext href="https://howtheyvote.eu/votes/195775">{t('status.sourceVotes')}</Ext>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
// PART 16 — the precedents timeline: 23 dated, sourced, citable events
|
||||
// (src/data/events.json + per-locale prose). Fully server-rendered; the only
|
||||
// "interaction" is the native :target highlight when an entry is cited via
|
||||
// its #tl-<id> anchor. Every source goes through Ext / the md helper, so the
|
||||
// rel policy holds everywhere.
|
||||
import { useT, type Locale } from '../i18n/config'
|
||||
import { loadDataset, type EventKind } from '../lib/content'
|
||||
import { mdToHtml } from '../lib/md'
|
||||
import Ext from './Ext.astro'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
|
||||
const events = [...loadDataset('events', locale)].sort((a, b) => a.date.localeCompare(b.date))
|
||||
|
||||
// kind → chip color (design tokens); labels come from the dictionaries
|
||||
const KIND_CLASS: Record<EventKind, string> = {
|
||||
promise: 'border-lvl2 text-lvl2',
|
||||
creep: 'border-warn text-warn',
|
||||
struck: 'border-ok text-ok',
|
||||
revealed: 'border-info text-info',
|
||||
}
|
||||
---
|
||||
|
||||
<section id="precedents">
|
||||
<p class="eyebrow">{t('sections2.precedents.eyebrow')}</p>
|
||||
<h2>{t('sections2.precedents.title')}</h2>
|
||||
<p class="text-dim">{t('sections2.precedents.intro')}</p>
|
||||
<ol class="m-0 mt-6 list-none border-s border-rule-strong p-0 ps-5">
|
||||
{
|
||||
events.map((ev) => (
|
||||
<li id={`tl-${ev.id}`} class="tl-item relative my-6 p-2">
|
||||
<p class="m-0 flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<a href={`#tl-${ev.id}`} class="font-mono text-sm font-bold no-underline">
|
||||
<time datetime={ev.date}>{ev.date}</time>
|
||||
</a>
|
||||
<span
|
||||
class={`rounded-dossier border px-1.5 py-0.5 font-mono text-xs tracking-wide uppercase ${KIND_CLASS[ev.kind]}`}
|
||||
>
|
||||
{t(`sections2.precedents.kinds.${ev.kind}`)}
|
||||
</span>
|
||||
</p>
|
||||
<h3 class="mt-1 mb-0 text-base">{ev.title}</h3>
|
||||
<p class="my-1 text-sm leading-6" set:html={mdToHtml(ev.body)} />
|
||||
<p class="mt-1 mb-0 font-mono text-xs">
|
||||
<Ext href={ev.src.href} class="text-dim">
|
||||
{ev.src.label} →
|
||||
</Ext>
|
||||
</p>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* the dot on the timeline rail */
|
||||
.tl-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: calc(-1.25rem - 3.5px);
|
||||
top: 1.05rem;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
}
|
||||
/* :target = the cited entry — stamp it so the deep link is visible */
|
||||
.tl-item:target {
|
||||
background: var(--surface);
|
||||
outline: 1px solid var(--accent);
|
||||
border-radius: var(--radius-dossier);
|
||||
}
|
||||
</style>
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
// Tool card shell. Language-neutral facts (name, links, level, icon…) come
|
||||
// from src/data/tools.json, looked up by the `tool` id prop — the same ids the
|
||||
// legacy page used (#t-signal, …). Localized prose arrives through named
|
||||
// slots, mirroring the legacy card structure:
|
||||
// name — display name, only when the legacy name carried localized text
|
||||
// tags — the tag row (level, "replaces X", jurisdiction/licence)
|
||||
// what — "What it's for"
|
||||
// why — "Why it matters"
|
||||
// who — "For whom & when"
|
||||
// extra — optional callout between the fields and the install steps
|
||||
// install — the step list of the "Install & use" disclosure
|
||||
// links — optional localized footer links (neutral ones come from JSON)
|
||||
// The field labels below reproduce the legacy CSS-generated label strings.
|
||||
import tools from '../data/tools.json'
|
||||
import { BRAND_ICONS } from '../icons.generated'
|
||||
import { defaultLocale, type Locale } from '../i18n/config'
|
||||
|
||||
interface Props {
|
||||
tool: string
|
||||
}
|
||||
|
||||
const { tool: toolId } = Astro.props
|
||||
const tool = tools.find((t) => t.id === toolId)
|
||||
if (!tool) {
|
||||
throw new Error(`ToolCard: unknown tool id "${toolId}" (not in src/data/tools.json)`)
|
||||
}
|
||||
|
||||
// Tile colors live in src/styles/brands.generated.css (generated from
|
||||
// tools.json by scripts/gen-icons.mjs) — a stylesheet, because the
|
||||
// production CSP (style-src 'self') blocks style= attributes.
|
||||
const iconPath = tool.iconSlug ? BRAND_ICONS[tool.iconSlug] : undefined
|
||||
|
||||
const locale = (Astro.currentLocale ?? defaultLocale) as Locale
|
||||
|
||||
const LABELS: Record<Locale, { what: string; why: string; who: string; install: string }> = {
|
||||
en: {
|
||||
what: "What it's for",
|
||||
why: 'Why it matters',
|
||||
who: 'For whom & when',
|
||||
install: 'Install & use',
|
||||
},
|
||||
fr: {
|
||||
what: 'À quoi ça sert',
|
||||
why: 'Pourquoi',
|
||||
who: 'Pour qui & quand',
|
||||
install: 'Installer & utiliser',
|
||||
},
|
||||
nl: {
|
||||
what: 'Waarvoor dient het',
|
||||
why: 'Waarom het ertoe doet',
|
||||
who: 'Voor wie & wanneer',
|
||||
install: 'Installeren & gebruiken',
|
||||
},
|
||||
}
|
||||
const labels = LABELS[locale]
|
||||
---
|
||||
|
||||
<article class="tool-card" id={tool.id} data-level={tool.level ?? undefined}>
|
||||
<header class="tool-head">
|
||||
<span class="tool-logo" aria-hidden="true">
|
||||
{
|
||||
iconPath ? (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d={iconPath} />
|
||||
</svg>
|
||||
) : (
|
||||
tool.monogram
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<div class="tool-heading">
|
||||
<h3 class="tool-name">{Astro.slots.has('name') ? <slot name="name" /> : tool.name}</h3>
|
||||
{
|
||||
Astro.slots.has('tags') && (
|
||||
<div class="tool-tags">
|
||||
<slot name="tags" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<p class="tool-field"><b class="tool-label">{labels.what}</b> <slot name="what" /></p>
|
||||
<p class="tool-field"><b class="tool-label">{labels.why}</b> <slot name="why" /></p>
|
||||
<p class="tool-field"><b class="tool-label">{labels.who}</b> <slot name="who" /></p>
|
||||
<slot name="extra" />
|
||||
{
|
||||
Astro.slots.has('install') && (
|
||||
<details class="tool-install">
|
||||
<summary>{labels.install}</summary>
|
||||
<slot name="install" />
|
||||
</details>
|
||||
)
|
||||
}
|
||||
<footer class="tool-foot">
|
||||
<slot name="links" />
|
||||
{
|
||||
tool.links.map((link) => (
|
||||
<a class="tool-link" href={link.href} target="_blank" rel="noopener noreferrer">
|
||||
{link.label} →
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<span class="tool-note">{tool.profiles.join('')}</span>
|
||||
</footer>
|
||||
</article>
|
||||
@@ -1,108 +0,0 @@
|
||||
---
|
||||
import { localeFlags, localeNames, localePath, locales, useT, type Locale } from '../i18n/config'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const t = useT(locale)
|
||||
---
|
||||
|
||||
<header class="topbar sticky top-0 z-40 border-b border-rule bg-surface/95 backdrop-blur-sm">
|
||||
<div class="flex items-center gap-4 px-4 py-2">
|
||||
<a
|
||||
href={`${localePath(locale)}/`.replace('//', '/')}
|
||||
class="font-mono text-sm font-bold tracking-wide no-underline"
|
||||
>
|
||||
EXIT CHAT CONTROL
|
||||
</a>
|
||||
<a
|
||||
href={`${localePath(locale)}/#toc`.replace('//', '/')}
|
||||
class="font-mono text-xs text-dim no-underline hover:text-accent"
|
||||
>
|
||||
{t('nav.toc')}
|
||||
</a>
|
||||
<a
|
||||
href={`${localePath(locale)}/quiz`.replace('//', '/')}
|
||||
class="font-mono text-xs text-dim no-underline hover:text-accent"
|
||||
>
|
||||
{t('nav.quiz')}
|
||||
</a>
|
||||
<div class="ms-auto flex items-center gap-3">
|
||||
<nav aria-label={t('nav.language')}>
|
||||
<details class="lang-menu" data-lang-menu>
|
||||
<summary
|
||||
class="cursor-pointer rounded-dossier border border-rule bg-surface-2 px-2 py-1 font-mono text-xs font-bold"
|
||||
>
|
||||
<span aria-hidden="true">{localeFlags[locale]}</span>
|
||||
{locale.toUpperCase()}
|
||||
</summary>
|
||||
<ul class="lang-list">
|
||||
{
|
||||
locales.map((l) => (
|
||||
<li>
|
||||
<a
|
||||
href={`${localePath(l)}/`.replace('//', '/')}
|
||||
data-lang-choice={l}
|
||||
aria-current={l === locale ? 'page' : undefined}
|
||||
class="lang-option"
|
||||
>
|
||||
<span aria-hidden="true">{localeFlags[l]}</span>
|
||||
{localeNames[l]}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</details>
|
||||
</nav>
|
||||
<button
|
||||
type="button"
|
||||
data-theme-toggle
|
||||
aria-label={t('nav.themeToggle')}
|
||||
class="cursor-pointer rounded-dossier border border-rule bg-surface-2 px-2 py-1 font-mono text-xs text-ink hover:border-accent"
|
||||
>
|
||||
◐
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
// Delegated once per page lifecycle — survives ClientRouter swaps.
|
||||
// Language choice: remember the reader's explicit pick so the root
|
||||
// boot script honors it on the next visit.
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement
|
||||
const langLink = target.closest<HTMLElement>('[data-lang-choice]')
|
||||
if (langLink) {
|
||||
try {
|
||||
localStorage.setItem('ecc-lang', langLink.dataset.langChoice ?? '')
|
||||
} catch {
|
||||
/* private mode: choice simply not persisted */
|
||||
}
|
||||
return
|
||||
}
|
||||
// close the language dropdown when clicking anywhere outside it
|
||||
for (const menu of document.querySelectorAll<HTMLDetailsElement>('[data-lang-menu][open]')) {
|
||||
if (!menu.contains(target)) menu.removeAttribute('open')
|
||||
}
|
||||
const toggle = target.closest<HTMLElement>('[data-theme-toggle]')
|
||||
if (toggle) {
|
||||
const root = document.documentElement
|
||||
const current =
|
||||
root.getAttribute('data-theme') ||
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
const next = current === 'dark' ? 'light' : 'dark'
|
||||
root.setAttribute('data-theme', next)
|
||||
try {
|
||||
localStorage.setItem('ecc-theme', next)
|
||||
} catch {
|
||||
/* private mode: theme applies for this page only */
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export {}
|
||||
</script>
|
||||
@@ -1,18 +0,0 @@
|
||||
import { defineCollection, z } from 'astro:content'
|
||||
import { glob } from 'astro/loaders'
|
||||
|
||||
// One entry per guide section per locale (src/content/sections/<locale>/NN-<slug>.mdx).
|
||||
// `id` is the LEGACY anchor id of the section (e.g. "menace", "messagerie"):
|
||||
// it must be preserved on the rendered <section> wrapper so every historical
|
||||
// deep link keeps working.
|
||||
const sections = defineCollection({
|
||||
loader: glob({ pattern: '**/*.mdx', base: './src/content/sections' }),
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
part: z.number(),
|
||||
order: z.number(),
|
||||
title: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
export const collections = { sections }
|
||||
@@ -1,159 +0,0 @@
|
||||
---
|
||||
id: 'menace'
|
||||
part: 0
|
||||
order: 1
|
||||
title: 'Understand the threat'
|
||||
---
|
||||
|
||||
<p class="part-num">PART 00 · THE WHY</p>
|
||||
|
||||
## Understand the threat
|
||||
|
||||
Before you change tools, understand what you are protecting against. Otherwise you install random apps and feel safe when you are not.
|
||||
|
||||
### Chat Control 1.0 and 2.0, plainly
|
||||
|
||||
"Chat Control" is the nickname for two EU texts that allow (or would require) scanning your private messages for child sexual abuse material (CSAM). The stated goal is legitimate; the method, blanket scanning of the communications of unsuspected people, amounts to mass surveillance.
|
||||
|
||||
<div class="tablewrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Chat Control 1.0</th>
|
||||
<th>Chat Control 2.0 (CSAR)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Text</td>
|
||||
<td>Regulation (EU) 2021/1232, ePrivacy derogation</td>
|
||||
<td>Proposal COM/2022/209 (Ylva Johansson, May 2022)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Scanning</td>
|
||||
<td>
|
||||
Voluntary, platforms <em>may</em> scan
|
||||
</td>
|
||||
<td>Mandatory via "detection orders" and "risk-mitigation measures"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Who</td>
|
||||
<td>
|
||||
Mostly unencrypted US services: Gmail, Messenger/Instagram, Skype, Snapchat, iCloud Mail,
|
||||
Xbox
|
||||
</td>
|
||||
<td>
|
||||
<strong>All</strong> platforms, including end-to-end encrypted messengers
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### Client-side scanning: the legal wiretap
|
||||
|
||||
The heart of Chat Control 2.0 is a technique called **client-side scanning**. The idea: instead of breaking encryption in transit, software is installed **directly on your phone** to inspect every message, photo or link **before** it is encrypted and sent. Signal put it in one line: it is "like malware on your device".
|
||||
|
||||
<div class="box honest">
|
||||
<span class="lab">Technical honesty, read twice</span>
|
||||
<p>
|
||||
Against client-side scanning, <strong>neither a VPN nor end-to-end encryption is enough</strong>{' '}
|
||||
if the app or the operating system cooperates. The scan happens on the device, before
|
||||
encryption: the VPN has nothing to protect, and encryption kicks in too late. What actually
|
||||
protects you is choosing{' '}
|
||||
<strong>free/open-source software that refuses to implement scanning</strong>, favourable{' '}
|
||||
<strong>jurisdictions</strong>, <strong>self-hosting</strong>, and{' '}
|
||||
<strong>taking back control of your device</strong> (an untampered OS). Everything else in this
|
||||
guide follows from this truth.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">Worst of all: the tool doesn't even work</span>
|
||||
<p>
|
||||
Everyone's privacy is sacrificed for a technology that fails. The European Parliament's own
|
||||
study concludes no system detects this content without a high error rate (because across
|
||||
billions of messages, even a tiny false-positive rate yields millions of false accusations).
|
||||
Irish police figures show it: of automated reports, only about 20% were actual material, and
|
||||
over 11% were outright false positives. Landmark research ("Bugs in Our Pockets," 2021) further
|
||||
shows that once client-side scanning is installed, it is inevitably repurposed (terrorism,
|
||||
copyright, opinions).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
### The pretext: "protecting children"
|
||||
|
||||
No one is against protecting minors, which is exactly what makes it such an effective lever. Fighting child abuse serves as an **emotional Trojan horse**: who would dare object? Under that banner, a principle is made acceptable that, on its own, would be flatly rejected, the automated inspection of the private communications of **hundreds of millions of unsuspected people**. Children are the stated motive; the real target is everyone's encryption and privacy.
|
||||
|
||||
The tell: the companies pushing hardest for this scanning are the ones whose business model **is** surveillance. On 19 March 2026, a joint appeal urged EU lawmakers to entrench "voluntary" detection, signed by:
|
||||
|
||||
<div class="namewall" aria-label="Google, LinkedIn, Snapchat, Microsoft, TikTok, Meta">
|
||||
<span>Google</span>
|
||||
<span>LinkedIn</span>
|
||||
<span>Snapchat</span>
|
||||
<span>Microsoft</span>
|
||||
<span>TikTok</span>
|
||||
<span>Meta</span>
|
||||
</div>
|
||||
|
||||
<blockquote class="pull">
|
||||
"Failure to do so would be irresponsible." The tech giants' argument for keeping message scanning
|
||||
alive.
|
||||
</blockquote>
|
||||
|
||||
Those same players even announced they would **keep scanning** messages after the legal basis lapsed on 4 April 2026. Citizens are thus asked to entrust the inspection of their intimate conversations to the very companies known for harvesting and monetising their data. This is the real face of Chat Control: an **attack on privacy** wrapped in a motive no one can refuse.
|
||||
|
||||
### This is not "just" surveillance
|
||||
|
||||
They try to reassure you: "it's only Gmail, Instagram, Snapchat, Messenger." That's false, and it's the most alarming part. Three shifts hide behind that downplaying.
|
||||
|
||||
1. **It's not targeting, it's dragnet.** This doesn't watch suspects: it installs systematic collection of entire populations' communications. Surveillance at state scale, continuously.
|
||||
2. **It's no longer reading, it's the power to block.** Software that inspects a message before it's sent can also **refuse to send it**. Client-side scanning builds the infrastructure of prior censorship: blocking speech before it even reaches its recipient.
|
||||
3. **Once the infrastructure exists, it gets repurposed.** A system built "for the children" becomes a general-purpose control tool. The motive changes; the machine stays.
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">The logical next step: the digital euro</span>
|
||||
<p>
|
||||
The same logic is about to reach your money. The ECB's digital euro rests on a{' '}
|
||||
<strong>traceable</strong> currency, bars companies from holding any, and comes with a{' '}
|
||||
<strong>holding cap</strong>: the ECB has floated about €3,000 per person as a working
|
||||
assumption, and the position voted in the European Parliament's committee (June 2026) leaves the
|
||||
cap for the Commission to set on the ECB's recommendation, reviewed every two years. The ECB
|
||||
swears the currency won't be "programmable," but the cap and the traceability are very much on
|
||||
the menu. When it lands, will you say "relax, it's only a cap"? That's the very same rhetorical
|
||||
trap as "it's only Gmail." The real question is never what is controlled today, but the control
|
||||
infrastructure being installed for tomorrow.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
### Which profile are you?
|
||||
|
||||
No tool is magic and nobody needs to do everything. Find your profile: each section tells you how far to go.
|
||||
|
||||
<div class="profiles">
|
||||
<div class="profile p1">
|
||||
<div class="lvl lvl-1">Beginner</div>
|
||||
<h4>🟢 The ordinary citizen</h4>
|
||||
<p>
|
||||
You just want your conversations and private life to stop being scanned and monetised. Goal:
|
||||
everyday privacy, without becoming an expert.
|
||||
</p>
|
||||
</div>
|
||||
<div class="profile p2">
|
||||
<div class="lvl lvl-2">Intermediate</div>
|
||||
<h4>🟡 The pseudonymous account</h4>
|
||||
<p>
|
||||
Information account, activist page, creator: you fear being deanonymised, doxxed, or losing
|
||||
your account. Goal: separate your real identity from your public one.
|
||||
</p>
|
||||
</div>
|
||||
<div class="profile p3">
|
||||
<div class="lvl lvl-3">Advanced</div>
|
||||
<h4>🔴 The whistleblower</h4>
|
||||
<p>
|
||||
Journalist, activist, source, facing a powerful adversary (state, employer). Goal: strong
|
||||
anonymity, anti-correlation, passing documents without being caught.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
id: 'memo'
|
||||
part: 0
|
||||
order: 2
|
||||
title: 'Delete / Adopt'
|
||||
---
|
||||
|
||||
<p class="part-num">PART 01 · MEMO · THE DIGITAL SWAP</p>
|
||||
|
||||
## Delete / Adopt
|
||||
|
||||
The big picture at a glance. Each tool is detailed in the numbered sections below.
|
||||
|
||||
<div class="swap">
|
||||
<div class="swap-col del">
|
||||
<div class="swap-h">Delete</div>
|
||||
<ul>
|
||||
<li>
|
||||
WhatsApp <b>(Meta)</b>
|
||||
</li>
|
||||
<li>
|
||||
Messenger <b>(Meta)</b>
|
||||
</li>
|
||||
<li>
|
||||
Instagram <b>(Meta)</b>
|
||||
</li>
|
||||
<li>Snapchat</li>
|
||||
<li>
|
||||
Skype <b>(Microsoft)</b>
|
||||
</li>
|
||||
<li>Discord</li>
|
||||
<li>
|
||||
Gmail <b>(Google)</b>
|
||||
</li>
|
||||
<li>
|
||||
Outlook <b>(Microsoft)</b>
|
||||
</li>
|
||||
<li>Google Drive / Photos</li>
|
||||
<li>
|
||||
OneDrive <b>(Microsoft)</b>
|
||||
</li>
|
||||
<li>TikTok</li>
|
||||
<li>Telegram (normal chats)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="swap-col keep">
|
||||
<div class="swap-h">Adopt</div>
|
||||
<ul>
|
||||
<li>Signal</li>
|
||||
<li>SimpleX Chat</li>
|
||||
<li>Session</li>
|
||||
<li>Bitchat</li>
|
||||
<li>
|
||||
Element <b>(Matrix)</b>
|
||||
</li>
|
||||
<li>Nostr</li>
|
||||
<li>Mastodon</li>
|
||||
<li>Proton Mail</li>
|
||||
<li>Tuta Mail</li>
|
||||
<li>Mullvad (VPN & browser)</li>
|
||||
<li>GrapheneOS</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,241 +0,0 @@
|
||||
---
|
||||
id: 'messagerie'
|
||||
part: 1
|
||||
order: 3
|
||||
title: 'Encrypted messaging'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 02 · FOUNDATIONS · <span class="lvl lvl-1">🟢</span>
|
||||
</p>
|
||||
|
||||
## Encrypted messaging
|
||||
|
||||
The first move, the one that protects you fastest: leave WhatsApp, Messenger and Telegram for a genuinely encrypted (ideally open-source) messenger.
|
||||
|
||||
<div class="tablewrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>You leave</th>
|
||||
<th>You adopt</th>
|
||||
<th>Why</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">WhatsApp</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Signal</span>
|
||||
</td>
|
||||
<td>Same simplicity, without Meta or metadata harvesting</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Messenger</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Signal / SimpleX</span>
|
||||
</td>
|
||||
<td>Messenger isn't E2EE by default and was scanned under Chat Control 1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Telegram</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Signal / SimpleX</span>
|
||||
</td>
|
||||
<td>Telegram isn't end-to-end encrypted by default</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ToolCard tool="t-signal">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces WhatsApp</span>
|
||||
<span class="tool-tag">🇺🇸 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
An encrypted messenger for your texts, calls, video and groups. The direct replacement for
|
||||
WhatsApp, just as simple.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
End-to-end encryption by default, non-profit, an audited protocol that is the world standard.
|
||||
Almost no metadata kept, and the team has pledged to leave the EU rather than install scanning.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone, starting today. It's the first and most useful step. One catch: a phone number, which
|
||||
you can hide behind a username.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install from <b>signal.org</b> (App Store, Google Play, or the direct APK on Android).
|
||||
</li>
|
||||
<li>Confirm your number by SMS, then set a PIN.</li>
|
||||
<li>
|
||||
Settings > Privacy: create a <b>username</b> to hide your number, and turn on{' '}
|
||||
<b>registration lock</b>.
|
||||
</li>
|
||||
<li>Invite your contacts. On desktop, link the desktop app to your phone.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-simplex">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡 Intermediate</span>
|
||||
<span class="tool-tag">No identifier</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
An encrypted messenger with no account, no number and no identifier. You connect by sharing a
|
||||
link or QR code.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
By design, even the servers can't know who talks to whom. It's currently the strongest option
|
||||
against metadata correlation.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Pseudonymous accounts, sensitive contacts, high-risk. A bit younger than Signal, with a few
|
||||
rough edges.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install from <b>simplex.chat</b> (F-Droid, Google Play, App Store or APK).
|
||||
</li>
|
||||
<li>
|
||||
Pick a <b>local</b> display name (never your real name: it stays on your device).
|
||||
</li>
|
||||
<li>
|
||||
To add a contact, share a <b>one-time invitation link</b> or a QR code.
|
||||
</li>
|
||||
<li>Turn on the passcode lock, and (advanced) configure your own SMP servers.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-session">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡 Intermediate</span>
|
||||
<span class="tool-tag">No phone</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Signal-grade encryption, but with no phone number and onion routing.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Your identity is a random "Session ID." Traffic goes through a decentralised network (Lokinet)
|
||||
that sharply reduces metadata.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
When a phone number is a liability. Smaller ecosystem, best kept for conversations that matter.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install from <b>getsession.org</b> (all platforms).
|
||||
</li>
|
||||
<li>
|
||||
"Create account" generates your Session ID and a <b>recovery phrase</b>.
|
||||
</li>
|
||||
<li>Write the recovery phrase on paper: it's the only key to your account.</li>
|
||||
<li>Share your Session ID (or QR) with your contacts.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-briar">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Offline / P2P</span>
|
||||
<span class="tool-tag">Android · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A peer-to-peer messenger that works <b>with no internet</b>: over Bluetooth, Wi-Fi Direct or
|
||||
Tor.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
No server, so nothing to shut down or seize. Survives internet shutdowns and network censorship.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Protests, disasters, remote areas, blackouts. Android only, and both people need the app.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install from <b>briarproject.org</b> or F-Droid (no iOS).
|
||||
</li>
|
||||
<li>
|
||||
Create a <b>local</b> account (nickname + password), stored only on the phone.
|
||||
</li>
|
||||
<li>Add a nearby contact by scanning their QR code, or remotely via a link.</li>
|
||||
<li>When the network is down, turn on Bluetooth: messages hop device to device.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-bitchat">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Bluetooth mesh</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Jack Dorsey's mesh messenger: nearby phones relay messages hop by hop, with no internet, server
|
||||
or account.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
End-to-end encryption (AES-256-GCM) and a "panic mode" that wipes everything with three taps on
|
||||
the logo.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Protests and blackouts, alongside Briar. Use with caution.</Fragment>
|
||||
<div class="box warn" slot="extra">
|
||||
<span class="lab">Caution: unaudited</span>
|
||||
<p>
|
||||
Its own repo warns it "has not received external security review." Great for resilience, but
|
||||
don't stake lives on it yet.
|
||||
</p>
|
||||
</div>
|
||||
<ol slot="install">
|
||||
<li>Install from the App Store (iOS) or the APK/GitHub (Android).</li>
|
||||
<li>Open the app and pick a nickname. No account to create.</li>
|
||||
<li>Nearby devices auto-discover over Bluetooth.</li>
|
||||
<li>In danger, triple-tap the logo to wipe everything (panic mode).</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-molly">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Hardened Signal</span>
|
||||
<span class="tool-tag">Android · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A hardened build of Signal for Android, compatible with the same network.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Database encrypted at rest, automatic locking, and a "FOSS" variant fully free of Google
|
||||
services.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Advanced users, especially on GrapheneOS, with a high threat model. Android only.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Add the Molly repo in F-Droid, or grab the APK from <b>molly.im</b>.
|
||||
</li>
|
||||
<li>
|
||||
Choose <b>Molly-FOSS</b> if you want zero Google dependencies.
|
||||
</li>
|
||||
<li>Register with your number (same network as Signal).</li>
|
||||
<li>Set a database password and automatic locking.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<div class="box">
|
||||
<span class="lab">Against Chat Control</span>
|
||||
<p>
|
||||
Prefer <strong>open-source</strong> apps <strong>outside the big US platforms</strong>, whose
|
||||
teams have publicly pledged to leave the EU rather than install client-side scanning (Signal
|
||||
has). Reminder: if the OS itself enforces scanning, the app can't help, hence section 12.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
id: 'email'
|
||||
part: 2
|
||||
order: 4
|
||||
title: 'Encrypted email & PGP'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 03 · <span class="lvl lvl-1">🟢</span>
|
||||
</p>
|
||||
|
||||
## Encrypted email & PGP
|
||||
|
||||
Gmail reads your email and lives in the US, under "Five Eyes" jurisdiction. Leaving Gmail/Outlook is a huge win for minimal effort.
|
||||
|
||||
<ToolCard tool="t-protonmail">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces Gmail</span>
|
||||
<span class="tool-tag">🇨🇭 · open-source · OpenPGP</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
An encrypted inbox that replaces Gmail or Outlook, and the pivot account for the whole Proton
|
||||
ecosystem.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Based in Switzerland, outside "Five Eyes" jurisdiction. OpenPGP-compatible: automatic encryption
|
||||
between Proton accounts and to other PGP services.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone. It's the best starting point to leave Google (see section 05 for the rest of the
|
||||
suite).
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Create an account at <b>proton.me</b> (the free tier is enough to start).
|
||||
</li>
|
||||
<li>
|
||||
Import your old messages and contacts with <b>Easy Switch</b> (built in).
|
||||
</li>
|
||||
<li>Set up forwarding from Gmail and tell your contacts your new address.</li>
|
||||
<li>
|
||||
Install the mobile app, turn on <b>two-factor auth</b>, and use Proton Bridge if you want
|
||||
Thunderbird.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-tuta">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces Gmail</span>
|
||||
<span class="tool-tag">🇩🇪 · open-source · post-quantum</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A German email service, fully open-source and free of any Google service.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It also encrypts <b>the subject line and some metadata</b>, with post-quantum-resistant
|
||||
encryption. EU jurisdiction.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want maximum encrypted metadata and fully free software. It doesn't use PGP: to write
|
||||
encrypted to outsiders, you password-protect the message.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Create an account at <b>tuta.com</b> and install the app (available on F-Droid).
|
||||
</li>
|
||||
<li>
|
||||
To write encrypted to a non-user: click the lock and set a password, shared with your
|
||||
correspondent through another channel.
|
||||
</li>
|
||||
<li>Turn on two-factor authentication.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-mailbox">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Ethical classic mail</span>
|
||||
<span class="tool-tag">🇩🇪 · UE · PGP</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Two ethical, low-cost European email providers, with server-side PGP support.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Privacy- and eco-conscious, built on open standards (IMAP, PGP). They work with any mail client.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want a "classic" mailbox (read in Thunderbird) but ethical, rather than a closed
|
||||
encrypted app.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Create an account (paid, roughly €1 to €3 per month).</li>
|
||||
<li>Enable server-side PGP encryption (the "Guard" feature at mailbox.org).</li>
|
||||
<li>Set the account up in Thunderbird or the mobile app of your choice.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
#### PGP in one minute
|
||||
|
||||
**PGP** (Pretty Good Privacy) is key-based encryption: you have a _public_ key you hand out, and a _private_ key you keep secret. Anyone with your public key can send you a message that **only you** can decrypt. It's the basis of provider-independent encrypted email. Proton Mail handles it automatically; for manual use, **OpenPGP** works via Thunderbird (built in since v78).
|
||||
@@ -1,148 +0,0 @@
|
||||
---
|
||||
id: 'navigateur'
|
||||
part: 3
|
||||
order: 5
|
||||
title: 'Browser & search'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 04 · <span class="lvl lvl-1">🟢</span>
|
||||
</p>
|
||||
|
||||
## Browser & search
|
||||
|
||||
Chrome is an advertising tracker. Google Search profiles every query. Replace both.
|
||||
|
||||
<ToolCard tool="t-mullvadbrowser">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴 Interm. / Advanced</span>
|
||||
<span class="tool-tag">Anti-fingerprint</span>
|
||||
<span class="tool-tag">🇸🇪 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The Tor Browser's browser, but without the Tor network: use it with your VPN for a hard-to-track
|
||||
desktop.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Built with the Tor Project, it's designed so all its users present the{' '}
|
||||
<b>same digital fingerprint</b>. The result: instead of being unique and thus trackable, you
|
||||
blend into the crowd. It's the best privacy choice on desktop.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want to go beyond simple ad-blocking, especially pseudonymous accounts and exposed
|
||||
profiles.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Download it from <b>mullvad.net/browser</b> (Windows, macOS, Linux).
|
||||
</li>
|
||||
<li>
|
||||
Use it as-is, without customising: every added extension or setting makes you identifiable
|
||||
again.
|
||||
</li>
|
||||
<li>Pair it with your VPN to also mask your IP address.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-firefox">
|
||||
<Fragment slot="name">Firefox (hardened)</Fragment>
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces Chrome</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">The reference free browser for privacy-respecting daily use.</Fragment>
|
||||
<Fragment slot="why">
|
||||
Unlike Chrome (a Google advertising tracker), Firefox is built by a foundation with no interest
|
||||
in profiling you. With a few tweaks (the <em>arkenfox</em> guide) and the uBlock Origin
|
||||
extension, it becomes very solid.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Everyone, as a main daily browser.</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install Firefox from <b>mozilla.org/firefox</b>.
|
||||
</li>
|
||||
<li>
|
||||
Add the <b>uBlock Origin</b> extension (ad and tracker blocking).
|
||||
</li>
|
||||
<li>
|
||||
In privacy settings, choose "Strict," disable telemetry, and clear cookies on close. To go
|
||||
further, apply the arkenfox guide.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-brave">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Blocks everything by default</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A Chromium-based browser that blocks ads and trackers by default, with a bonus private window
|
||||
connected to Tor.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It's the simplest option for someone coming from Chrome: same feel, but without the tracking. It
|
||||
blocks YouTube ads natively, offers encrypted video (Brave Talk, based on Jitsi) and one-click
|
||||
Tor access, handy to reach a blocked site.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Beginners who want a painless switch from Chrome. (Ignore the crypto/rewarded-ads features if
|
||||
they don't interest you.)
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install Brave from <b>brave.com</b>.
|
||||
</li>
|
||||
<li>Set "Shields" to "aggressive," and turn off Brave Rewards if you don't want ads.</li>
|
||||
<li>For Tor: menu > "New private window with Tor," then wait for "Tor connected".</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-ublock">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Extension</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The browser extension that blocks ads, trackers and spy scripts. The highest-return move, in ten
|
||||
seconds.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Online advertising is the main vector of surveillance and profiling. Blocking it cuts most
|
||||
tracking, speeds up pages and reduces malware risk.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Everyone, on Firefox as well as Chromium browsers.</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Open your browser's extension store and search "uBlock Origin" (the original, by Raymond
|
||||
Hill).
|
||||
</li>
|
||||
<li>Install it. It works right away, no setup needed.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-search">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢🟡 Search engines</span>
|
||||
<span class="tool-tag">Replaces Google Search</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">Search engines that don't profile every query, unlike Google.</Fragment>
|
||||
<Fragment slot="why">
|
||||
DuckDuckGo and Brave Search keep no history tied to your identity. Startpage serves Google
|
||||
results without the tracking. SearXNG goes further: a <b>self-hostable</b> metasearch engine,
|
||||
sovereignty down to your search bar.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Everyone. SearXNG targets those who want to host their own engine.</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
In your browser settings, change the default engine to DuckDuckGo, Brave Search or Startpage.
|
||||
</li>
|
||||
<li>
|
||||
For SearXNG: use a public instance (directory at searx.space) or host your own (see section
|
||||
10).
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
id: 'dns'
|
||||
part: 3
|
||||
order: 6
|
||||
title: 'Encrypted DNS & Cloudflare'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 05 · NETWORK · DNS · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Encrypted DNS & Cloudflare
|
||||
|
||||
DNS is the directory that turns a site name (example.com) into a machine address. The catch: by default your internet provider runs that directory, so it sees **every site you visit**, even when the page is HTTPS. Encrypting your DNS takes that list back from your ISP. It's a quiet setting but one of the most effective.
|
||||
|
||||
<ToolCard tool="t-quad9">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Malware-blocking</span>
|
||||
<span class="tool-tag">🇨🇭 non-profit</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
An encrypted, Swiss, non-profit DNS resolver that also blocks known malicious domains.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Swiss jurisdiction, no retention of your IP address, protection against phishing and malware. An
|
||||
excellent "set and forget" choice.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone, especially those who want simplicity and a good jurisdiction.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
On Android: Settings > Network > <b>Private DNS</b> > enter <b>dns.quad9.net</b>.
|
||||
</li>
|
||||
<li>
|
||||
On iPhone/Mac: install the DoH profile from quad9.net. On PC: set DNS-over-HTTPS in the
|
||||
browser or the system.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-mullvaddns">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡 Intermediate</span>
|
||||
<span class="tool-tag">Network filtering</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Two encrypted resolvers that also block ads and trackers for the whole device.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
<b>Mullvad DNS</b> (Sweden, no-log) is free and offers blocklists. <b>NextDNS</b> is highly
|
||||
customisable, with a dashboard and per-device profiles, but it's US-based and logs by default
|
||||
(turn it off in settings).
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want system-wide ad-blocking without installing a per-browser extension.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Mullvad DNS: use the address shown at mullvad.net/help/dns as your private DNS (same method as
|
||||
Quad9).
|
||||
</li>
|
||||
<li>
|
||||
NextDNS: create a profile at nextdns.io, <b>disable logging</b>, then apply the config to your
|
||||
devices.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-cloudflare">
|
||||
<Fragment slot="name">Cloudflare 1.1.1.1 (with nuance)</Fragment>
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Fast but centralised</span>
|
||||
<span class="tool-tag">🇺🇸</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Cloudflare's 1.1.1.1 encrypted DNS and WARP app: fast, free, and a real upgrade over your ISP's
|
||||
resolver.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It's useful, with an audited no-logging pledge. But be clear-eyed: Cloudflare already sits in
|
||||
front of a huge share of the web (as a technical middleman, it can see plaintext traffic to
|
||||
those sites). Routing your DNS there too means <b>concentrating even more of your footprint</b>{' '}
|
||||
with one US company under US law.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Fine for everyday use and malware-blocking (1.1.1.1 for Families). Avoid or diversify for
|
||||
sensitive use: prefer Quad9 or Mullvad DNS instead.
|
||||
</Fragment>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-pihole">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Whole-network blocking</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
An ad and tracker blocker that protects <b>your whole network at once</b>, installed on a small
|
||||
computer like a Raspberry Pi.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Where uBlock Origin protects one browser, Pi-hole filters <b>every device</b> in the home,
|
||||
including those where you can't install an extension: phones, smart TVs, connected objects. Ads
|
||||
and trackers are blocked at the DNS level, before they even load. <b>AdGuard Home</b> is an
|
||||
alternative with a more modern interface.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Households that want to clean up all their devices at once, and who have (or want) a Raspberry
|
||||
Pi or small server.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>On a Raspberry Pi (or a container), run Pi-hole's official installer.</li>
|
||||
<li>In your router's settings, set the Pi-hole's address as the DNS server.</li>
|
||||
<li>From now on, every device on your network is filtered automatically.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
@@ -1,170 +0,0 @@
|
||||
---
|
||||
id: 'vpn'
|
||||
part: 4
|
||||
order: 7
|
||||
title: 'VPN: the truth'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 06 · <span class="lvl lvl-1">🟢</span>
|
||||
</p>
|
||||
|
||||
## VPN: the truth
|
||||
|
||||
A VPN is useful, but sold with a lot of lies. Here's what it actually protects.
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">What a VPN does NOT do</span>
|
||||
<p>
|
||||
A VPN <strong>does not protect you from Chat Control's client-side scanning</strong>: that reads
|
||||
your messages on your device, before encryption and therefore before the VPN. It's also not an
|
||||
invisibility cloak: your OS and apps see more than your VPN provider does.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="box ok">
|
||||
<span class="lab">What a VPN does well</span>
|
||||
<p>
|
||||
It hides your activity from your <strong>ISP</strong> and masks your <strong>IP address</strong>{' '}
|
||||
(and thus location) from the sites you visit. Useful against network surveillance, geolocation
|
||||
and censorship. One piece of the puzzle, not the solution.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ToolCard tool="t-mullvad">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢🟡🔴 All levels</span>
|
||||
<span class="tool-tag">No account · cash/Monero</span>
|
||||
<span class="tool-tag">🇸🇪 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A VPN that masks your IP address and encrypts your traffic up to its servers, without ever
|
||||
asking for a single piece of personal information.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It's the privacy gold standard. No email, no name: at signup you're given a plain account
|
||||
number. Audited no-log policy, a single honest price (€5 per month, no tiers, no false
|
||||
promises), and payment in cash or Monero. That's exactly what matters the day a state tries to
|
||||
cut off VPN access: an account that can't be tied to your identity, and a payment banks can't
|
||||
block.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone, from the citizen bypassing an ID check to the high-risk profile. It's the VPN to
|
||||
recommend first.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Go to <b>mullvad.net</b>. If the site is blocked in your country, open it through the Tor
|
||||
Browser or its <b>.onion</b> address. Click "Generate account number": no email or name is
|
||||
asked. Save this number in your password manager and never share it, it's your only access
|
||||
key.
|
||||
</li>
|
||||
<li>
|
||||
Choose the duration and payment method. The price is flat. Three routes: bank card (simplest
|
||||
if not blocked), cash sent by post, or cryptocurrency.
|
||||
</li>
|
||||
<li>
|
||||
<b>Anonymous crypto payment:</b> click "Create a one-time payment address." Mullvad shows an
|
||||
address (Bitcoin or Monero) and an exact amount. Send that amount from your wallet. For
|
||||
maximum anonymity, prefer <b>Monero</b> (private by default); Bitcoin is simpler but
|
||||
traceable. See section 09 to buy crypto. Allow about 30 minutes for confirmation.
|
||||
</li>
|
||||
<li>
|
||||
Download the app from <b>mullvad.net/download</b> (via Tor if the site is filtered) and
|
||||
install it. On mobile, use the App Store, Google Play, or the official APK if the app was
|
||||
pulled from your store.
|
||||
</li>
|
||||
<li>
|
||||
Launch the app, enter your account number, then connect to a server{' '}
|
||||
<b>outside your country</b>, ideally outside the EU (Switzerland, US…). Enable the "kill
|
||||
switch" (which cuts the internet if the VPN drops) and Mullvad's DNS.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-protonvpn">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Honest free tier</span>
|
||||
<span class="tool-tag">🇨🇭 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The VPN from the Proton ecosystem, with a genuine free tier (rare and honest).
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Swiss-based, audited, open-source. Its free tier has no ads, no data resale and no data cap
|
||||
(only the number of countries is limited). It's the ideal on-ramp for a beginner, especially if
|
||||
you already use Proton Mail.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Beginners, and anyone already in the Proton ecosystem who wants a single account for everything.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Go to <b>protonvpn.com</b> and sign in with your Proton account (or create one).
|
||||
</li>
|
||||
<li>Install the app (Windows, macOS, Linux, Android, iOS).</li>
|
||||
<li>
|
||||
Use "Quick Connect" or pick a country, then enable the kill switch and NetShield (ad and
|
||||
tracker blocking).
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-ivpn">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴 Interm. / Advanced</span>
|
||||
<span class="tool-tag">No email · no-log</span>
|
||||
<span class="tool-tag">🇬🇮 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A small, especially rigorous and transparent VPN provider, on the same model as Mullvad.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Audited no-log policy, no mandatory email, payment in crypto or cash. It even offers
|
||||
anti-tracking options (network-level tracker blocking) and signup with no identifier at all.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
A solid alternative to Mullvad, for pseudonymous and high-risk profiles who want to diversify.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
At <b>ivpn.net</b>, generate an account (an ID is created for you, no email).
|
||||
</li>
|
||||
<li>Pay in crypto or cash, as with Mullvad.</li>
|
||||
<li>
|
||||
Install the app, connect outside the EU, enable the kill switch and the WireGuard protocol.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-tailscale">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Private access to your server</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A private encrypted network linking your devices to your server, without ever exposing it on the
|
||||
public internet.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Instead of opening ports (and getting attacked), <b>Tailscale</b> builds a WireGuard tunnel
|
||||
between your authorised devices only: your server has no public attack surface. <b>Headscale</b>{' '}
|
||||
is the self-hosted version, so you depend on no third party. <b>WireGuard</b> alone gives full
|
||||
control, at the cost of manual setup.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Every self-hoster. It's the safe way to reach your Nextcloud or photos from outside.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install Tailscale on your server and on your phone/PC, connect them to the same account.
|
||||
</li>
|
||||
<li>
|
||||
Your devices instantly see each other on a private network; reach your server at its Tailscale
|
||||
address.
|
||||
</li>
|
||||
<li>For zero dependency, replace the coordination server with self-hosted Headscale.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
id: 'censure'
|
||||
part: 4
|
||||
order: 8
|
||||
title: 'Censorship & identity checks'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 07 · CENSURE · DIGITAL IDENTITY · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Censorship & identity checks
|
||||
|
||||
The same pretext ("protect minors") is used to force identity checks to access the internet. It's the planned end of online anonymity, and therefore a form of censorship. Here's the state of play, then how to escape it lawfully.
|
||||
|
||||
#### Where we stand (2025-2026)
|
||||
|
||||
- **France.** The SREN law (May 2024) tasks regulator Arcom with age verification; since April 2025, covered sites must implement it. A decree imposing age checks was upheld by the Conseil d'État on 15 July 2025, and a bill banning under-15s from social media passed the Assemblée on 26 January 2026, which means verifying the age, and thus the identity, of everyone.
|
||||
- **UK.** Since 25 July 2025, the Online Safety Act requires "highly effective" age verification (ID, credit card, facial estimation). The immediate result: VPN signups jumped over 1,400% within hours. The House of Lords even debated, in early 2026, an age limit on VPN use itself.
|
||||
- **EU.** The Commission unveiled an age-verification app (April 2025), piloted in five countries, designed to plug into the European digital identity wallet (EUDI Wallet, eIDAS 2) that every state must offer by end of 2026. EFF and EDRi warn: the "zero-knowledge" proof is only optional, and this builds an online-ID infrastructure far beyond child protection.
|
||||
|
||||
#### Escaping it lawfully
|
||||
|
||||
The counter comes down to one idea: take an IP address in a country that doesn't impose these checks, and obtain the tools anonymously, even if they get blocked one day.
|
||||
|
||||
- **An offshore VPN.** Connect to a server outside the filtering country (ideally outside the EU and the "14 Eyes"), with an audited no-log policy. This restores access to networks demanding an ID.
|
||||
- **Anonymous payment.** Pay for the VPN in crypto or cash (Mullvad, IVPN, Proton accept it). Crucial: if VPNs ever get "banned," payment processors could refuse cards; cash and crypto would be the only options left.
|
||||
- **Tor to fetch a blocked VPN.** If the provider's site becomes unreachable from your country, use the Tor Browser (or a .onion address) to download the client. Once installed, the VPN is far faster for daily use.
|
||||
- **Switch your store country.** An Apple/Google account set to another country brings back apps pulled from your local store.
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">The law is shifting, check</span>
|
||||
<p>
|
||||
Bypassing a geo-restriction is legal in most European countries, but the ground shifts fast:
|
||||
Utah (US) has criminalised VPN circumvention, and the UK Lords are eyeing VPN limits. Check your
|
||||
country's law before relying on any method. This guide is about protecting privacy, not hiding a
|
||||
crime.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
id: 'proton'
|
||||
part: 5
|
||||
order: 9
|
||||
title: 'Leave Google'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 08 · TAKE BACK CONTROL · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Leave Google
|
||||
|
||||
Google is a single account that knows your email, calendar, files, movements and searches. Replace the whole suite.
|
||||
|
||||
<div class="tablewrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Google</th>
|
||||
<th>Proton</th>
|
||||
<th>Others</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Gmail</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Proton Mail</span>
|
||||
</td>
|
||||
<td>Tuta, mailbox.org</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Google Drive</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Proton Drive</span>
|
||||
</td>
|
||||
<td>Nextcloud, Cryptomator</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Google Calendar</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Proton Calendar</span>
|
||||
</td>
|
||||
<td>Tuta Calendar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Google Password</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Proton Pass</span>
|
||||
</td>
|
||||
<td>Bitwarden, KeePassXC</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="old">Google One VPN</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="new">Proton VPN</span>
|
||||
</td>
|
||||
<td>Mullvad, IVPN</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
The appeal of **Proton** (Swiss, non-profit foundation, open-source): one account replaces all of Google, with end-to-end encryption and a jurisdiction outside "Five Eyes". You can migrate gradually, service by service. If you'd rather not recentralise on one provider, every row has an independent alternative.
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
id: 'stockage'
|
||||
part: 6
|
||||
order: 10
|
||||
title: 'Encrypted storage'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 09 · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Encrypted storage
|
||||
|
||||
<ToolCard tool="t-protondrive">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces Google Drive / iCloud</span>
|
||||
<span class="tool-tag">🇨🇭 · E2EE</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
An end-to-end encrypted cloud for your files and photos, part of Proton.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Unlike Google Drive or iCloud, Proton can't read your files: they're encrypted before leaving
|
||||
your device. Swiss jurisdiction, same account as Proton Mail.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone, as a simple, direct replacement for Google's or Apple's cloud.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Enable Proton Drive in your Proton account and install the app.</li>
|
||||
<li>Turn on automatic photo backup, then disable Google's/Apple's.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-cryptomator">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡 Intermediate</span>
|
||||
<span class="tool-tag">Encrypt before upload</span>
|
||||
<span class="tool-tag">🇩🇪 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A vault that encrypts your files <b>before</b> uploading them to any cloud, even Google Drive or
|
||||
Dropbox.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
The perfect trick if you must keep an existing cloud: you keep the convenient service, but the
|
||||
provider only sees encrypted gibberish. You alone hold the key.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who can't leave a mainstream cloud yet, but want to protect their files right now.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Install Cryptomator (desktop and mobile) from cryptomator.org.</li>
|
||||
<li>Create a "vault" in your cloud's synced folder, choose a strong password.</li>
|
||||
<li>Put your files in the vault: they're encrypted then synced automatically.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-nc-storage">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Your own cloud</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Your own cloud (files, calendar, contacts, notes) hosted on your server.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
The ultimate degree of control: your data never leaves your hardware. Detailed in section 10
|
||||
(Self-hosting).
|
||||
</Fragment>
|
||||
<Fragment slot="who">Self-hosters. See the full card in section 10.</Fragment>
|
||||
<a slot="links" class="tool-link" href="#selfhost">
|
||||
→ Section 10
|
||||
</a>
|
||||
</ToolCard>
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
id: 'motsdepasse'
|
||||
part: 7
|
||||
order: 11
|
||||
title: 'Password manager'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 10 · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Password manager
|
||||
|
||||
One strong, unique password per service: the foundation of everything that follows. Drop the notebook, drop the reused password.
|
||||
|
||||
<ToolCard tool="t-bitwarden">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Best to start with</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A password vault that remembers, generates and fills your passwords for you, across all your
|
||||
devices.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Free, audited, cross-platform, and end-to-end encrypted. You only need to remember one strong
|
||||
password. It's even <b>self-hostable</b> (via Vaultwarden) so you depend on no one.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone. It's the foundation of all security: one strong, unique password per service.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Create an account at bitwarden.com and choose a <b>long</b> master password (a phrase). Never
|
||||
forget it: it can't be recovered.
|
||||
</li>
|
||||
<li>Install the browser extension and the mobile app.</li>
|
||||
<li>
|
||||
As you log in, let Bitwarden save and then replace your old passwords with generated ones.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-keepassxc">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">100% local</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A <b>local</b> vault: an encrypted file only you hold, with no cloud and no account.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Nothing leaves your device, unless you choose to sync the file yourself (for example via
|
||||
Cryptomator). It's the purist's choice, with no middleman at all.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Advanced profiles who want full control and zero online dependency.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install KeePassXC (desktop) and a compatible mobile app (KeePassDX on Android, Strongbox on
|
||||
iOS).
|
||||
</li>
|
||||
<li>Create a database file protected by a master password.</li>
|
||||
<li>To have it on all devices, sync that file through an encrypted cloud.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-protonpass">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">With email aliases</span>
|
||||
<span class="tool-tag">🇨🇭 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The password manager of the Proton ecosystem, with built-in disposable email aliases.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Ideal if you're already on Proton: one account for mail, cloud and passwords. Its email aliases
|
||||
save you from giving your real address everywhere.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Those who want everything in one place with Proton.</Fragment>
|
||||
</ToolCard>
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
id: 'deuxfa'
|
||||
part: 7
|
||||
order: 12
|
||||
title: 'Two-factor auth & hardware keys'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 11 · SECURITY · 2FA · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Two-factor auth & hardware keys
|
||||
|
||||
A password, even a strong one, can leak. Two-factor authentication (2FA) adds a second proof at login: even if your password is stolen, the account stays locked. It's essential on your sensitive accounts (email, password manager, social).
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">Avoid SMS 2FA</span>
|
||||
<p>
|
||||
A code sent by SMS is the weak link: it's vulnerable to "SIM-swap" (an attacker gets your number
|
||||
transferred) and to interception over the phone network (SS7). Studies estimate most SIM-swap
|
||||
attempts succeed. Use SMS only as a last resort, never as your main protection.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ToolCard tool="t-yubikey">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴 Interm. / Advanced</span>
|
||||
<span class="tool-tag">FIDO2 hardware key</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A small physical key (USB, often with NFC) that proves your identity with a single touch.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It's the strongest 2FA: it resists phishing, because the key checks the site's real address
|
||||
before responding. <b>Nitrokey</b> is the fully open-hardware, open-firmware alternative. A fake
|
||||
site can't trick you.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want maximum security on key accounts. Buy <b>two</b> (one main, one backup).
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Get two keys from yubico.com or nitrokey.com.</li>
|
||||
<li>
|
||||
In each account's security settings (email, password manager…), add a "security key" or
|
||||
"passkey." Enrol both keys.
|
||||
</li>
|
||||
<li>Keep the backup key in a safe, separate place.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-aegis">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">TOTP codes</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Apps that generate the six-digit codes changing every 30 seconds, replacing Google
|
||||
Authenticator.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
<b>Aegis</b> (Android) keeps your codes in a local encrypted vault, no cloud. <b>Ente Auth</b>{' '}
|
||||
adds end-to-end encrypted sync across devices, and has been audited. Far safer than SMS, and
|
||||
free.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone: it's the default 2FA level to adopt, ahead of hardware keys.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Install Aegis (F-Droid) or Ente Auth (all platforms).</li>
|
||||
<li>On each account, enable "authenticator app" 2FA and scan the QR code shown.</li>
|
||||
<li>
|
||||
Write down the <b>backup codes</b> on paper and make an encrypted backup of your vault.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
@@ -1,122 +0,0 @@
|
||||
---
|
||||
id: 'social'
|
||||
part: 8
|
||||
order: 13
|
||||
title: 'Decentralised social'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 12 · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Decentralised social
|
||||
|
||||
Instagram, X and Facebook belong to companies that profile you and can censor or delete you overnight. The "fediverse" offers networks owned by their users.
|
||||
|
||||
<ToolCard tool="t-mastodon">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces X / Twitter</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The X/Twitter alternative: a social network made of thousands of independent servers that talk
|
||||
to each other (the "fediverse").
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
No single owner, no algorithm deciding for you, no arbitrary ban by one company. The feed is
|
||||
chronological, with no ads or manipulation.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone. Ideal for information accounts that want a presence no company can cut off.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
On joinmastodon.org, choose a server (or "instance") that suits you, then create an account.
|
||||
</li>
|
||||
<li>
|
||||
Install an app (the official one, or Ivory, Tusky…) and follow accounts: you see the whole
|
||||
fediverse, whatever their server.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-nostr">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Un-censorable</span>
|
||||
<span class="tool-tag">🌍 · open protocol</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Not an app but a protocol: a social network where your identity is a simple key pair, like
|
||||
Bitcoin.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
No email, no password, no account anyone can delete. Your posts travel across decentralised
|
||||
relays and you truly own your audience: no one can ban you for good. You can even receive
|
||||
Bitcoin tips ("zaps") and switch apps anytime without losing your followers.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Information accounts and creators who fear platform censorship and want a presence truly their
|
||||
own.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Install a client: Damus (iPhone), Amethyst or Primal (Android), or a web version.</li>
|
||||
<li>
|
||||
The app generates your key pair. <b>Back up your private key (nsec)</b> like a Bitcoin seed
|
||||
phrase: on paper, never shared.
|
||||
</li>
|
||||
<li>Create your profile, follow people, enable zaps to receive sats.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-element">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Replaces Discord / Slack</span>
|
||||
<span class="tool-tag">🌍 · federated · E2EE</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Community chat, in the spirit of Discord or Slack, but federated and end-to-end encryptable.
|
||||
Element is the app, Matrix the network.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
You can host your own server (Synapse) and control your data. Perfect for a collective, a
|
||||
newsroom or an association that wants its own independent space.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Communities and groups. See the metadata caveat below.</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install Element (element.io) and create an account on a public server, or on your own (section
|
||||
10).
|
||||
</li>
|
||||
<li>Join or create rooms, and enable end-to-end encryption for private conversations.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-fediverse">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡</span>
|
||||
<span class="tool-tag">Insta · YouTube · Reddit</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The federated cousins of Instagram (Pixelfed), YouTube (PeerTube) and Reddit (Lemmy).
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Same logic as Mastodon: no single owner, no ad algorithm, no tracking. You post photos, videos
|
||||
or discussions without feeding a profiling machine.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want to leave Instagram, YouTube or Reddit without giving up the format.
|
||||
</Fragment>
|
||||
</ToolCard>
|
||||
|
||||
<div class="box">
|
||||
<span class="lab">Metadata caveat</span>
|
||||
<p>
|
||||
Matrix encrypts <em>content</em> well, but in federation, participating servers see room
|
||||
membership and message timing. For highly sensitive use, prefer SimpleX/Session or self-host
|
||||
your server.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
id: 'argent'
|
||||
part: 9
|
||||
order: 14
|
||||
title: 'Financial sovereignty'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 13 · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Financial sovereignty
|
||||
|
||||
Money is surveilled and censorable too. Activists' accounts have been frozen, donations blocked, and every card payment is tracked. Financial privacy is part of sovereignty.
|
||||
|
||||
<ToolCard tool="t-bitcoin">
|
||||
<Fragment slot="name">Bitcoin (self-custody)</Fragment>
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Censorship-resistant</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">A digital money you hold yourself, without going through a bank.</Fragment>
|
||||
<Fragment slot="why">
|
||||
In <b>self-custody</b> (your keys in a wallet only you control), no one can freeze or block your
|
||||
funds. Its privacy isn't perfect (the ledger is public), but it escapes banking censorship,
|
||||
useful the day a payment is refused.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Guarding against account freezes or de-banking, and paying anonymously for a service like a VPN.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Get some bitcoin (an exchange, or peer-to-peer).</li>
|
||||
<li>
|
||||
Move them right away to a wallet <b>you</b> control: a hardware wallet (Trezor, ColdCard) or
|
||||
an open-source app. Don't leave them on the exchange.
|
||||
</li>
|
||||
<li>Back up your recovery phrase on paper, never as a photo or online.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-monero">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Private by default</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
The privacy cryptocurrency: amounts, sender and recipient are hidden by default.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It's the exact opposite of Bitcoin here: transactions are private by design. It's the best way
|
||||
to pay for a service without leaving a trail back to you.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want maximum financial privacy. Note: Monero is already delisted from several European
|
||||
exchanges, so you often need a decentralised swap.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Install a wallet (the official one at getmonero.org, or Cake Wallet on mobile).</li>
|
||||
<li>
|
||||
Get XMR through a decentralised exchange service (for example a "swap" from another crypto).
|
||||
</li>
|
||||
<li>Back up your recovery phrase on paper.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">Legality & caution</span>
|
||||
<p>
|
||||
Financial privacy is <strong>legal</strong>. Don't confuse it with tax evasion: declare what
|
||||
must be declared. Cryptocurrencies are <strong>volatile</strong> and scams are common; never
|
||||
risk what you can't lose, and learn before you act.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,115 +0,0 @@
|
||||
---
|
||||
id: 'ia'
|
||||
part: 9
|
||||
order: 15
|
||||
title: 'Conversational AI'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 14 · BLIND SPOT · THE AI MADNESS · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## Conversational AI
|
||||
|
||||
While we fight to encrypt our messages, hundreds of millions of people pour their most intimate thoughts into ChatGPT, Gemini or Copilot, on US servers. This is the blind spot of the whole story. Sheer madness.
|
||||
|
||||
What you type into a cloud AI is a **written, timestamped, stored confession**. Worse than a message: you spill your anxieties, your health, your finances, your work secrets, often more honestly than to a friend. And unlike an encrypted messenger, it's all readable in plaintext on the server.
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">What the facts say (2025-2026)</span>
|
||||
<p>
|
||||
Consumer tiers <strong>train on your data by default</strong>. In January 2026, a US judge
|
||||
ordered OpenAI to produce <strong>20 million ChatGPT conversations</strong> as evidence: the
|
||||
affected users were neither notified nor consulted. Deleting a chat{' '}
|
||||
<strong>does not guarantee</strong> it's gone. And in one leak, 47,000 exposed conversations
|
||||
held emails, phone numbers and re-identifiable intimate details.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
#### The counter: local AI
|
||||
|
||||
<ToolCard tool="t-localai">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Replaces ChatGPT</span>
|
||||
<span class="tool-tag">🌍 · 100% local</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">Software to run an AI model directly on your own computer.</Fragment>
|
||||
<Fragment slot="why">
|
||||
Your questions never leave the device: no server, no account, no telemetry, and it works
|
||||
offline. Quality depends on your hardware, but nothing leaks. <b>Ollama</b> and <b>LM Studio</b>{' '}
|
||||
are the easiest; <b>Jan</b> and <b>GPT4All</b> aim for maximum privacy.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Anyone who uses AI for even slightly personal topics and doesn't want to hand them to a US
|
||||
server.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
For a gentle start, install <b>LM Studio</b> or <b>Jan</b> (a graphical interface, like a
|
||||
normal app).
|
||||
</li>
|
||||
<li>
|
||||
Download a model offered in the app (for example Llama or Mistral), matched to your machine's
|
||||
power.
|
||||
</li>
|
||||
<li>
|
||||
Chat away: everything happens locally. Ollama is the command-line version for the more
|
||||
technical.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-pocketpal">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Replaces ChatGPT on mobile</span>
|
||||
<span class="tool-tag">📱 · 100% local</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">An app that runs AI models directly on your phone, fully offline.</Fragment>
|
||||
<Fragment slot="why">
|
||||
Your prompts never leave the phone: no account, no cloud, no telemetry, and it works in airplane
|
||||
mode. Open source (MIT), it downloads <b>GGUF</b> models (Gemma, Qwen, Phi, Llama) from Hugging
|
||||
Face and runs them on your device's CPU, GPU or NPU. The mobile counterpart to Ollama and LM
|
||||
Studio.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Anyone who reaches for ChatGPT on their phone for personal questions and doesn't want to hand
|
||||
them to a US server.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Install <b>PocketPal</b> from the App Store or Google Play (or build it from source).
|
||||
</li>
|
||||
<li>
|
||||
Open <b>Models</b>, pick one that fits your phone, and download it.
|
||||
</li>
|
||||
<li>Load it and chat: everything runs offline, on the device.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-cloudai">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢🟡</span>
|
||||
<span class="tool-tag">Privacy-minded cloud</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Cloud AI access, but built for privacy, when a local AI isn't possible.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
<b>Duck.ai</b> (DuckDuckGo) gives anonymised access to several models. <b>Lumo</b> (Proton)
|
||||
encrypts exchanges and doesn't train on them. Far better than consumer ChatGPT, but it's still a
|
||||
remote server.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want cloud convenience without feeding the giants. Keep non-sensitive topics there.
|
||||
</Fragment>
|
||||
</ToolCard>
|
||||
|
||||
<div class="box honest">
|
||||
<span class="lab">The simple rule</span>
|
||||
<p>
|
||||
Never paste into a cloud AI what you wouldn't tell a stranger who's recording: identity, health,
|
||||
passwords, work secrets, confessions. For anything sensitive, use a local AI.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,192 +0,0 @@
|
||||
---
|
||||
id: 'boiteaoutils'
|
||||
part: 9
|
||||
order: 16
|
||||
title: 'The everyday toolbox'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 15 · REPLACE THE REST · <span class="lvl lvl-2">🟡</span>
|
||||
</p>
|
||||
|
||||
## The everyday toolbox
|
||||
|
||||
Google and Apple are not just email: maps, notes, calendar, photos, video calls, everything is tied to your identity. Here's how to replace each brick, one at a time, without losing convenience.
|
||||
|
||||
<ToolCard tool="t-maps">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢 Beginner</span>
|
||||
<span class="tool-tag">Replaces Google Maps</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Map and navigation apps, built on OpenStreetMap, that work offline.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Google Maps logs every trip and builds a chillingly precise location history.{' '}
|
||||
<b>Organic Maps</b> tracks nothing and shows no ads; <b>OsmAnd</b> targets power users (hiking,
|
||||
contour lines, detailed navigation).
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone. Live traffic and transit are less complete than Google's, but everyday navigation is
|
||||
excellent.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Install from F-Droid, the App Store or Google Play.</li>
|
||||
<li>Download the maps of the regions you need in advance for offline use.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-notes">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢🟡</span>
|
||||
<span class="tool-tag">Replaces Docs / Notion</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
End-to-end encrypted notes and documents, to replace Google Docs, Notion or Evernote.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
<b>Standard Notes</b> (by Proton) encrypts your notes by default. <b>Joplin</b> handles Markdown
|
||||
notebooks with optional encryption and the sync of your choice. <b>CryptPad</b> (French) offers
|
||||
a real-time, fully encrypted Google Docs equivalent.
|
||||
</Fragment>
|
||||
<Fragment slot="who">Everyone for notes; CryptPad for multi-person collaboration.</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Standard Notes / Joplin: create an account, install the app, and enable encryption (on by
|
||||
default in Standard Notes, to switch on in Joplin).
|
||||
</li>
|
||||
<li>CryptPad: use cryptpad.fr or another instance, no account needed to start.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-visio">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢🟡</span>
|
||||
<span class="tool-tag">Replaces Zoom / Meet</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Video-conferencing tools with no account or heavy install, to replace Zoom, Google Meet or
|
||||
Teams.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
<b>Jitsi Meet</b> runs in the browser, no account, and can be self-hosted (it's also the basis
|
||||
of Brave Talk). <b>Element Call</b> is end-to-end encrypted and federated via Matrix. For a
|
||||
simple, fully encrypted call, <b>Signal</b> calls also do the job.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone. On a public Jitsi server, multi-party calls are transport-encrypted; end-to-end
|
||||
encryption is optional.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Jitsi: go to meet.jit.si, create a room name, share the link. Nothing to install.</li>
|
||||
<li>Element Call: from the Element (Matrix) app, start a call in a room.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-photos">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Replaces Google Photos</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Two ways to keep your photos without handing them to Google or Apple.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
<b>Ente</b> is a managed, end-to-end encrypted service, with on-device recognition and search:
|
||||
simple and safe. <b>Immich</b> is a Google Photos clone you <b>host yourself</b> (face and
|
||||
object search included), for those comfortable running a server.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Ente for everyone; Immich for self-hosters. Note: Immich is not end-to-end encrypted, its
|
||||
security depends on your server.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Ente: install the app, create an account, turn on automatic backup of your camera roll.</li>
|
||||
<li>
|
||||
Immich: deploy it on your server (see section 10) via Docker, then connect the mobile app.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-alias">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟢🟡</span>
|
||||
<span class="tool-tag">Email aliases</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Disposable email aliases: a unique address per site, forwarding to your real inbox without ever
|
||||
revealing it.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
You stop giving out your real address everywhere. If a site is breached or spams you, you cut
|
||||
that one alias, and you instantly know who leaked your data. <b>SimpleLogin</b> is integrated
|
||||
with Proton; <b>addy.io</b> offers unlimited aliases on the free tier.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone, and especially pseudonymous accounts that want to compartmentalise their signups.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Create an account, install the browser extension.</li>
|
||||
<li>At each signup, generate a new alias instead of typing your real address.</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-backups">
|
||||
<Fragment slot="name">Encrypted backups</Fragment>
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Cryptomator · restic</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A way to back up your phone and computer, encrypting the data <b>before</b> it leaves for any
|
||||
cloud.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
iCloud/Google backups are often accessible to the provider, and thus to courts.{' '}
|
||||
<b>Cryptomator</b> encrypts your files before sending them to any cloud; <b>restic</b> makes
|
||||
automatic encrypted backups; <b>Proton Drive</b> is end-to-end encrypted. On iPhone, enable
|
||||
"Advanced Data Protection" to encrypt your iCloud backups.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Everyone should encrypt their backups. Golden rule: two encrypted copies, on two media, in two
|
||||
places.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>iPhone: Settings > your name > iCloud > Advanced Data Protection: turn it on.</li>
|
||||
<li>
|
||||
PC: install Cryptomator, create a vault, put your files in it before syncing to the cloud.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-databrokers">
|
||||
<Fragment slot="name">Erase your data (GDPR)</Fragment>
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡</span>
|
||||
<span class="tool-tag">Data brokers</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Getting your personal information removed from the "data brokers" that collect and resell it.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
In Europe, the GDPR (Article 17) gives you a <b>right to erasure</b>: you can demand, for free,
|
||||
that a company delete your data, in principle within a month. Services like <b>Incogni</b>{' '}
|
||||
automate these requests at scale, for a fee.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Those who want to reduce their exposure. Note: data reappears over time, and this doesn't touch
|
||||
public records or social media. Doing it yourself is free but tedious.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>For a manual request: write to the broker citing GDPR Article 17 and ask for deletion.</li>
|
||||
<li>
|
||||
To automate: subscribe to a removal service and let it send the requests on your behalf.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
@@ -1,123 +0,0 @@
|
||||
---
|
||||
id: 'selfhost'
|
||||
part: 10
|
||||
order: 17
|
||||
title: 'Self-hosting'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 16 · ADVANCED SELF-DEFENCE · <span class="lvl lvl-3">🔴</span>
|
||||
</p>
|
||||
|
||||
## Self-hosting
|
||||
|
||||
The ultimate level of sovereignty: host your services yourself. Your data lives on your hardware, under the jurisdiction you choose.
|
||||
|
||||
<ToolCard tool="t-yunohost">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Turnkey server</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Systems that turn an old PC or a Raspberry Pi into a personal server, with an app catalogue you
|
||||
install in a few clicks.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Self-hosting sounds scary, but these tools do the heavy lifting. <b>YunoHost</b> even runs your
|
||||
own email and single sign-on. <b>Umbrel</b> offers a 300+ app store, popular with privacy and
|
||||
Bitcoin folks. <b>Start9</b> encrypts backups and gives every service a Tor address by default.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Advanced profiles ready to spend an afternoon building their own cloud. It's the most radical
|
||||
structural counter to Chat Control.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Get an old computer, a Raspberry Pi, or rent a small server (VPS) in a protective country.
|
||||
</li>
|
||||
<li>Install YunoHost, Umbrel or Start9 following their official guide.</li>
|
||||
<li>From the catalogue, install Nextcloud, a Matrix server, a photo gallery, etc.</li>
|
||||
<li>
|
||||
Reach it privately with Tailscale (VPN section) rather than exposing the server to the public.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-nextcloud">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Replaces all of Google</span>
|
||||
<span class="tool-tag">🇩🇪 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Your own cloud: files, calendar, contacts, photos, notes and collaborative documents, on your
|
||||
server.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
It's the full replacement for Google Drive, Calendar and Contacts, but at home. You choose the
|
||||
jurisdiction and no one else controls your files. Note: it's "your server" privacy, not
|
||||
end-to-end encryption by default; you must keep it updated.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Self-hosters. The easiest path is to install it via YunoHost or Umbrel.
|
||||
</Fragment>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-synapse">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Your chat server</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
Your own Matrix chat server, powering Element and encrypted calls.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
By hosting the server, you also control the <b>metadata</b> (who talks to whom, when), Matrix's
|
||||
weak point in federation. It's community chat that you alone own.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Communities, collectives, newsrooms that want their own independent federated space.
|
||||
</Fragment>
|
||||
</ToolCard>
|
||||
|
||||
<ToolCard tool="t-website">
|
||||
<Fragment slot="name">Your own website</Fragment>
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🟡🔴</span>
|
||||
<span class="tool-tag">Ban-proof your voice</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A site of your own, on your own domain name, where your content depends on no platform.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
The day a network deletes or suspends your account, all your work vanishes. A personal site is
|
||||
your anchor: your posts stay there, archived and up to date, whatever happens. It's exactly what
|
||||
cautious information accounts do. A static site (built with Hugo, Astro or Jekyll) is enough and
|
||||
publishes almost for free; for a dynamic blog, a self-hosted WordPress or Ghost does the job.
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
Information accounts, creators, activists, anyone who publishes and fears platform censorship.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>Buy a domain name (ideally from a privacy-respecting registrar).</li>
|
||||
<li>
|
||||
Choose a static-site generator (Astro, Hugo) and host the output, or install WordPress/Ghost
|
||||
on your server.
|
||||
</li>
|
||||
<li>
|
||||
Always point to it from your social accounts: your audience finds you even if one account
|
||||
falls.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<div class="box">
|
||||
<span class="lab">The jurisdiction point</span>
|
||||
<p>
|
||||
Self-hosting also means choosing <em>where</em> your data lives. A server at home or in a
|
||||
protective country escapes the scanning duties imposed on big platforms. It's the most radical
|
||||
structural counter to Chat Control.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: 'tor'
|
||||
part: 11
|
||||
order: 18
|
||||
title: 'Tor'
|
||||
---
|
||||
|
||||
<p class="part-num">
|
||||
PART 17 · <span class="lvl lvl-3">🔴</span>
|
||||
</p>
|
||||
|
||||
## Tor
|
||||
|
||||
The network that separates who you are from what you do online.
|
||||
|
||||
<ToolCard tool="t-tor">
|
||||
<Fragment slot="tags">
|
||||
<span class="tool-tag">🔴 Advanced</span>
|
||||
<span class="tool-tag">Network anonymity</span>
|
||||
<span class="tool-tag">🌍 · open-source</span>
|
||||
</Fragment>
|
||||
<Fragment slot="what">
|
||||
A network that separates who you are from what you do online, accessible via the Tor Browser.
|
||||
</Fragment>
|
||||
<Fragment slot="why">
|
||||
Your traffic bounces through several encrypted relays worldwide: none knows both your identity
|
||||
and your destination. It's the tool of journalists and sources, and the way to reach a blocked
|
||||
site (for example to download a censored VPN).
|
||||
</Fragment>
|
||||
<Fragment slot="who">
|
||||
High-risk profiles, and anyone who must decouple their activity from their real IP. Use it
|
||||
occasionally, not as an everyday browser.
|
||||
</Fragment>
|
||||
<ol slot="install">
|
||||
<li>
|
||||
Download the Tor Browser from <b>torproject.org</b> (Windows, macOS, Linux, Android).
|
||||
</li>
|
||||
<li>Launch it and click "Connect." If Tor is blocked in your country, enable a "bridge."</li>
|
||||
<li>
|
||||
Browse without maximising the window, without installing extensions, and{' '}
|
||||
<b>without logging into your real accounts</b>.
|
||||
</li>
|
||||
</ol>
|
||||
</ToolCard>
|
||||
|
||||
<div class="box warn">
|
||||
<span class="lab">Limits, not magic</span>
|
||||
<p>
|
||||
Tor protects transport, not your habits: log into your real account and you deanonymise
|
||||
yourself. It's slower, and the exit node sees unencrypted traffic (use HTTPS). For serious
|
||||
anonymity, pair it with Tails (section 12).
|
||||
</p>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user