Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5aacb0a
feat: Add a better and complete responsive interface on all pages
Buuuntyyy Apr 13, 2026
3d4f173
fix: ui-ux regressions following responsive update
Buuuntyyy Apr 13, 2026
dd19221
feat: now, authenticated user will be redirect to /dashboard by click…
Buuuntyyy Apr 13, 2026
6870a8d
fix: minor migration at startup
Buuuntyyy Apr 13, 2026
db99c49
feat: add S3 backup replication metrics and configuration for rclone
Buuuntyyy Apr 13, 2026
282487a
feat: added security page for security summary (zero-knowledge proof,…
Buuuntyyy Apr 13, 2026
ac6a964
feat: add support for rejected P2P transfers with notifications and U…
Buuuntyyy Apr 14, 2026
e2d3073
fix: Friends visibility was not reciprocal
Buuuntyyy Apr 14, 2026
2e00596
fix: comma
Buuuntyyy Apr 14, 2026
daaa3a3
feat: deleted P2P limit.
Buuuntyyy Apr 14, 2026
df5e942
fix: Search bar wasn't enable even though user didn't activate the pa…
Buuuntyyy Apr 15, 2026
7cd8c10
fix: reduce /api/v1/p2p/signals call (to get p2p demand). If websocke…
Buuuntyyy Apr 15, 2026
4b2ec5b
feat: added support for p2p subdomain.
Buuuntyyy Apr 15, 2026
4de349d
fix: back arrow will bring you back to your previous page dynamically
Buuuntyyy Apr 15, 2026
5c966cd
feat: added button to easily navigate to drive dashboard
Buuuntyyy Apr 15, 2026
ddfd41d
feat: Modified the login page for Send service, to avoid confusion wi…
Buuuntyyy Apr 15, 2026
7524e87
feat: User can now change his email, password is required.
Buuuntyyy Apr 16, 2026
74f33f9
feat: update navigation to use dynamic Drive URL based on subdomain
Buuuntyyy Apr 16, 2026
d6dba69
feat: enhance P2P transfer notifications with re-notify feature and t…
Buuuntyyy Apr 16, 2026
614a8e8
fix: presence was not bugged and some old p2p request appeared randomly
Buuuntyyy Apr 16, 2026
5776f24
fix: elements couldn't be named with unicode char.
Buuuntyyy Apr 16, 2026
318382e
fix: improve MFA parameter state with green and red colours
Buuuntyyy Apr 16, 2026
aba2661
fix: Modify wrong data about consumed stoarge in usage page
Buuuntyyy Apr 16, 2026
2bb2ede
fix: download counter wasn't incremented
Buuuntyyy Apr 16, 2026
2a79eb8
fix: change emoticone for icone, dialog now correctly appears
Buuuntyyy Apr 16, 2026
8228e0b
fix: improve P2P signal handling and polling fallback logic
Buuuntyyy Apr 17, 2026
49f8abe
fix:navbar now appears in navbar, except if user has activated filena…
Buuuntyyy Apr 18, 2026
83c3311
feat: add french and english readme. Static pages are now visible in …
Buuuntyyy Apr 20, 2026
e43e663
fix: some texts were not traducted in animations
Buuuntyyy Apr 20, 2026
d66d26f
fix: Rejected P2P transfer notifications was not working due to table…
Buuuntyyy Apr 20, 2026
0e24430
feat: implement guest mode for file transfer invites and enhance invi…
Buuuntyyy Apr 20, 2026
9997777
feat: implement guest authentication for P2P invites and enhance invi…
Buuuntyyy Apr 20, 2026
7e68472
fix: Mobile app was not ergonomic
Buuuntyyy Apr 20, 2026
b6961d4
fix: Home button for P2P now redirect to dedicated subdomain
Buuuntyyy Apr 20, 2026
200d656
feat: add legal consent check for guest file transfers and update pri…
Buuuntyyy Apr 20, 2026
d749092
feat: implement email encryption for user accounts and P2P invites
Buuuntyyy Apr 20, 2026
ecadef1
Feat: Filtering by extensions and tags (by directory). Searchbar size…
Buuuntyyy Apr 20, 2026
f7b26b9
fix: mobile ergonomy improved
Buuuntyyy Apr 20, 2026
422fcf1
feat: Improve searchbar behaviour. Research now send you to file loca…
Buuuntyyy Apr 21, 2026
c800e4f
fix: dependencies issues patch
Buuuntyyy Apr 21, 2026
b02ac07
fix: formatting
Buuuntyyy Apr 21, 2026
b8a58ae
fix: add EMAIL_ENCRYPTION_KEY to CI environment variables
Buuuntyyy Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ jobs:
DATABASE_URL: postgres://test:testpassword@localhost:5432/kagibi_test
REDIS_URL: redis://localhost:6379
JWT_SECRET: test-secret-key-for-ci
EMAIL_ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000"
PORT: 8080
run: |
go build -o server .
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ docker/.ovhkeys
docker/dump_inserts.sql
frontend/.env

scripts/k8s-rclone-cronjob.yaml

# Dependencies
node_modules/*
frontend/node_modules/
Expand Down
319 changes: 55 additions & 264 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,290 +1,81 @@
# Kagibi

**End-to-end encrypted cloud storage, without compromise.**
**Stockage cloud chiffré de bout en bout, sans compromis.**

Kagibi est une plateforme de stockage cloud conçue autour d'un principe simple : **ce que vous stockez ne regarde que vous**. Le serveur ne peut pas lire vos fichiers. Pas parce que nous promettons de ne pas le faire — mais parce que nous n'en avons pas la capacité technique.
Ce projet a été développé par [Buuuntyyy] avec l'aide d'intelligence artificielle pour certaines tâches de développement et de documentation. L'objectif est de fournir une solution de stockage sécurisée, respectueuse de la vie privée, et facile à utiliser, tout en étant transparente sur son fonctionnement interne.

---

## Philosophie

La plupart des solutions cloud chiffrent vos données *sur le serveur*, avec des clés que le fournisseur contrôle. En cas de faille, de réquisition judiciaire, ou d'abus interne, vos données sont exposées.

Kagibi fonctionne différemment. Vos fichiers sont chiffrés **sur votre appareil**, avant d'être envoyés. Le serveur ne reçoit que des blocs opaques. Votre clé de déchiffrement ne quitte jamais votre machine.

Ce modèle dit *zero-knowledge* a un coût : il n'est pas possible de récupérer vos fichiers si vous perdez votre mot de passe sans code de récupération. C'est un compromis assumé, pas un bug.

Kagibi est publié sous licence **AGPLv3** : le code est auditable, le déploiement est autonome si vous le souhaitez.

---

## Comment fonctionne le chiffrement

### Dérivation des clés

Quand vous créez un compte ou vous connectez, Kagibi dérive une **clé maître** (MasterKey) à partir de votre mot de passe :

```
Mot de passe + sel aléatoire (16 octets)
Argon2id (64 Mo mémoire, 4 passages)
KEK (Key Encryption Key) — reste en RAM, ne quitte jamais le navigateur
MasterKey — dérivée, stockée uniquement en RAM
```

La **MasterKey** chiffre ensuite tous vos fichiers et métadonnées. La **KEK** enveloppe la MasterKey pour la stocker côté serveur sous forme chiffrée (`EncryptedMasterKey`) — inutilisable sans votre mot de passe.

### Chiffrement des fichiers (upload)

Chaque fichier est découpé en **chunks de 10 Mo**, chiffrés individuellement avec **AES-256-GCM** :

```
Fichier original
Découpage en chunks de 10 Mo
Pour chaque chunk :
├── Nonce unique (8 octets aléatoires + 4 octets compteur)
├── AES-256-GCM encrypt
└── Format stocké : [Nonce 12B][Ciphertext][Tag 16B]
Upload direct vers S3 via URLs présignées (TTL 180s)
Le backend orchestre, mais ne touche jamais le contenu.
```

### Déchiffrement en streaming (téléchargement)

Le téléchargement ne reconstitue jamais le fichier entier en mémoire :

```
URL présignée S3 (TTL 5 min)
ReadableStream (fetch)
TransformStream : parse [Nonce][Ciphertext][Tag] → AES-GCM decrypt
FileSystemWritableFileStream ou Blob
(jamais stocké déchiffré de façon temporaire)
```

### Ce que le serveur ne peut pas faire

| Opération | Possible pour le serveur ? |
|-----------|---------------------------|
| Lire le contenu d'un fichier | Non — blobs opaques sur S3 |
| Lire le nom d'un fichier | Non si l'option est activée — voir ci-dessous |
| Déchiffrer les données d'un partage | Non — clés chiffrées avec RSA-OAEP |
| Accéder à votre clé maître | Non — jamais transmise au backend |

### Chiffrement des noms de fichiers (opt-in)

Lors de l'inscription, il est possible d'activer le chiffrement des noms de fichiers et dossiers. Cette option est indépendante du chiffrement du contenu (toujours actif).

**Quand l'option est désactivée (défaut)** : les noms sont stockés en clair en base de données et dans le bucket S3. La barre de recherche est fonctionnelle.

**Quand l'option est activée** :

```
Nom du fichier (ex. "rapport.pdf")
AES-256-GCM avec la MasterKey
IV aléatoire (12 octets, CSPRNG)
Encodage base64url (pas de padding)
→ "aB3xK7mQ..." (opaque, pas de caractère spécial)
┌─────┴─────┐
│ │
PostgreSQL S3 OVH
name = "aB3xK7..." users/{id}/enc_path/aB3xK7...
```

- Le navigateur déchiffre les noms localement à chaque chargement de répertoire.
- La barre de recherche est désactivée : les noms stockés étant des blobs opaques, une recherche `ILIKE` côté serveur est sans effet.
- Le choix est permanent à la création du compte.

---

## Les trois systèmes de partage

### 1. Partage par lien

Vous générez un lien public que n'importe qui peut ouvrir, sans compte.

**Fonctionnement :**

1. Kagibi génère une `ShareKey` aléatoire, puis chiffre la clé du fichier avec elle.
2. Un token aléatoire (32 octets) est créé et associé au lien.
3. Le lien peut être protégé par un mot de passe (haché en bcrypt) et/ou limité dans le temps (1 à 30 jours).
4. Le destinataire visite le lien, Kagibi lui retourne le blob chiffré et la `ShareKey`.
5. Son navigateur déchiffre le fichier localement.

Le serveur stocke : le token, la clé chiffrée avec la ShareKey, le hash du mot de passe optionnel, la date d'expiration. Il ne peut pas lire le fichier.

---

### 2. Partage avec un ami (utilisateur à utilisateur)

Le partage direct entre comptes utilise la cryptographie asymétrique pour garantir que seul le destinataire peut déchiffrer.

**Fonctionnement :**

1. À la création de compte, chaque utilisateur génère une paire de clés **RSA-OAEP 4096 bits**.
- La clé publique est stockée en clair sur le serveur.
- La clé privée est chiffrée avec la MasterKey, puis stockée sur le serveur.

2. Pour ajouter un ami, on utilise son **code ami** (8 caractères alphanumériques, ex. `#A7KD92XZ`), unique par compte.

3. Pour partager un fichier :
- Kagibi récupère la clé publique RSA du destinataire.
- La `FileKey` (clé AES du fichier) est chiffrée avec cette clé publique.
- Le résultat chiffré est stocké en base, rattaché au partage.

4. Quand le destinataire accède au fichier :
- Il récupère la `FileKey` chiffrée.
- Son navigateur la déchiffre avec sa clé privée RSA (déchiffrée elle-même avec sa MasterKey).
- Le fichier est déchiffré localement.

Le serveur stocke : la `FileKey` chiffrée (inutilisable sans la clé privée du destinataire), les relations d'amitié, les permissions.

---

### 3. Transfert P2P (appareil à appareil)

Le transfert P2P envoie des fichiers directement d'un appareil à un autre, sans passer par le stockage serveur.
**Toutefois, les réseaux internet modernes rendent souvent les connexions directes impossibles (NAT, pare-feu). Kagibi utilise un serveur TURN pour relayer les données quand nécessaire, mais le chiffrement de bout en bout est maintenu.**
Concrètement, les appareils établissent une connexion WebRTC DataChannel, et les données sont chiffrées en AES-GCM avant d'être envoyées. Le serveur ne voit que des flux de données chiffrés, même lors du relais TURN.

**Fonctionnement :**

1. Les deux appareils établissent une connexion **WebRTC DataChannel** via un serveur de signalisation (WebSocket).
2. Le backend relaie uniquement les messages de négociation WebRTC (offer/answer/ICE candidates), stockés temporairement dans la table `p2p_signals` pour livraison hors-ligne.
3. Une fois la connexion pair-à-pair établie, les données transitent **directement** entre les appareils, chiffrées en AES-GCM par la couche applicative.
4. Le serveur ne voit ni le contenu transféré, ni les métadonnées du fichier.

Les transferts P2P sont comptabilisés par plan utilisateur (`p2p_max_exchanges`).

---

## Ce qui est stocké sur le serveur

### Données de compte
Read the documentation in your language:

| Donnée | Format | Pourquoi |
|--------|--------|---------|
| Adresse email | Clair | Authentification |
| Nom d'affichage | Clair | Interface utilisateur |
| Mot de passe | bcrypt (coût 12) | Vérification à la connexion |
| Sel Argon2id | Aléatoire (16 octets) | Dérivation de la KEK côté client |
| `EncryptedMasterKey` | Chiffré (KEK) | Restauration de la MasterKey à la connexion |
| Clé publique RSA | Clair | Chiffrement des partages entrants |
| `EncryptedPrivateKey` | Chiffré (MasterKey) | Déchiffrement des partages reçus |
| Code de récupération | SHA-256 (hash) | Réinitialisation sans email |
| Code ami | Clair | Recherche d'amis |

### Métadonnées de fichiers

| Donnée | Format |
|--------|--------|
| Nom du fichier | Clair (défaut) ou Chiffré AES-GCM si option activée à l'inscription |
| Taille (octets) | Clair |
| Type MIME | Clair |
| Dates de création/modification | Clair |
| Clé de fichier (`EncryptedKey`) | Chiffré (MasterKey) |

### Données sociales et de partage

- Liste d'amis et statut (en attente / accepté)
- Partages actifs : identifiant de ressource + clé chiffrée + permissions
- Liens publics : token + clé chiffrée + expiration + hash de mot de passe optionnel
- Activités récentes (fichiers accédés — optionnel)

### Ce qui n'est pas collecté

- Contenu des fichiers (jamais en clair sur le serveur)
- Historique de navigation ou de recherche
- Adresses IP (sauf journalisation temporaire à des fins de sécurité/abus)
- Données analytiques ou pixels de tracking
- Informations sur l'appareil ou le navigateur
- [English](./docs/README.en.md)
- [Français](./docs/README.fr.md)

---

## Récupération de compte

Un code de récupération est généré à l'inscription. Il est distinct du mot de passe et permet de retrouver l'accès à la MasterKey si le mot de passe est perdu.

```
Code de récupération (8 caractères)
├── SHA-256(code) → stocké comme RecoveryHash (vérification)
└── Argon2id(code, recovery_salt) → déchiffre EncryptedMasterKeyRecovery
```

Si le code de récupération est également perdu, les données sont **définitivement inaccessibles**. Ce n'est pas un bug — c'est la garantie zero-knowledge.
## Features

### Security & Privacy
- **Zero-knowledge encryption** — files are encrypted on your device before upload; the server never sees plaintext content
- **AES-256-GCM** file encryption, split into 10 MB chunks with unique nonces
- **Argon2id** key derivation (64 MB memory, 4 iterations)
- **RSA-OAEP 4096-bit** key pair per user for asymmetric sharing
- **Optional filename encryption** — file and folder names can be encrypted client-side at registration
- **MFA (TOTP)** two-factor authentication support
- **Account recovery code** — password-independent recovery without server access to your keys

### File Management
- Upload, download, organize files and folders
- Streaming decryption — large files are never fully loaded into memory
- Recently opened files
- File search (disabled when filename encryption is active)

### Sharing
- **P2P transfer** — direct device-to-device file transfer over WebRTC DataChannel, encrypted with AES-GCM; a TURN relay (**no intermediate storage**, only relay) is used when a direct connection is not possible, without compromising encryption.
- **Public link sharing** — optional password protection and expiration (1–30 days)
- **Friend sharing (user-to-user)** — end-to-end encrypted via RSA; only the recipient can decrypt

### Social
- Friend system via unique friend codes (e.g. `#A7KD92XZ`)
- Real-time online presence
- P2P transfer notifications with sound and browser push

### Account & Settings
- Light / dark theme
- Multi-language interface (i18n)
- Usage dashboard (storage, P2P quota)
- GDPR-compliant data export and account deletion (30-day grace period)

---

## Suppression des données
## Roadmap

- La suppression d'un compte déclenche une **suppression logique** immédiate (marquage `deleted_at`).
- Un processus de nettoyage asynchrone effectue la **suppression physique définitive** au bout de 30 jours : lignes en base, blobs S3.
- Conformité RGPD (articles 17 et 20) : droit à l'effacement et à la portabilité.
> This section lists planned and considered features. It is not a commitment to a release date.

---

## Stack technique

| Composant | Technologie |
|-----------|-------------|
| Frontend | Vue 3.5, Vite 7, Pinia |
| Backend | Go 1.21+, Gin |
| Base de données | PostgreSQL 16+ |
| Cache / rate-limit | Redis 7+ |
| Stockage objet | OVH S3 (compatible AWS) |
| Chiffrement | AES-256-GCM, RSA-OAEP 4096, Argon2id |
| Authentification | JWT HS256, TOTP (MFA) |
| P2P | WebRTC DataChannel, TURN/STUN (Coturn) |
| Déploiement | Docker Compose (dev), Kubernetes / Rancher (prod) |
<!-- Add roadmap items below. Example format:
- [ ] Feature name — short description
- [x] Already shipped feature
-->

---

## Démarrage rapide (développement)

**Prérequis :** Docker, Docker Compose
## Quick start

```bash
git clone https://github.com/Buuuntyyy/SaferCloud.git
cd SaferCloud

git clone https://github.com/Buuuntyyy/Kagibi.git
cd Kagibi
cp backend/.env.example backend/.env
# Renseigner les variables S3, JWT_SECRET, etc.
cp frontend/.env.example frontend/.env

docker compose up -d
```

Frontend : `http://localhost` — Backend : `http://localhost:8080`

Pour la configuration détaillée (variables d'environnement, S3, Kubernetes), voir [`backend/README.md`](./backend/README.md).
cd backend
go run main.go

---
cd frontend
npm install
npm run dev
```

## Licence
Frontend: `http://localhost` — Backend: `http://localhost:8080`

AGPLv3 — voir [`LICENSE`](./LICENSE).
## License

Toute modification du code, y compris dans un contexte SaaS, doit être publiée sous la même licence.
AGPLv3 — see [`LICENSE`](./LICENSE).
15 changes: 15 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ AUTH_PROVIDER=local
# ===== JWT (required when AUTH_PROVIDER=local) =====
JWT_SECRET=change-me-use-a-long-random-string

# ===== EMAIL ENCRYPTION (required — all auth providers) =====
# Server-side AES-256-GCM encryption of email addresses at rest.
# A DB dump without this key cannot reveal any email address.
# Generate with: openssl rand -hex 32
# WARNING: changing this key requires re-encrypting all emails in the DB.
EMAIL_ENCRYPTION_KEY=change-me-generate-with-openssl-rand-hex-32

# ===== DATABASE =====
DATABASE_URL=postgres://user:password@localhost:5432/safercloud?sslmode=disable

Expand All @@ -37,6 +44,14 @@ S3_REGION=eu-west-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key

# ===== S3 BACKUP (réplication quotidienne à 01h00 Paris → bucket Infrequent Access) =====
# Seul S3_BACKUP_BUCKET est obligatoire — les autres héritent des valeurs S3_* si omis.
# S3_BACKUP_BUCKET= # defaut : S3_BUCKET + "-backup"
# S3_BACKUP_ENDPOINT= # défaut : S3_ENDPOINT
# S3_BACKUP_REGION= # défaut : S3_REGION
# S3_BACKUP_ACCESS_KEY= # défaut : S3_ACCESS_KEY
# S3_BACKUP_SECRET_KEY= # défaut : S3_SECRET_KEY

# ===== CORS =====
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000

Expand Down
Loading
Loading