Skip to main content
Glama
README.md
# sfrbox-toolkit

CLI, API REST et serveur MCP **non officiels** pour piloter la configuration
d'une box SFR (firmware `NB6VAC`, ex. « SFR Box 7 » fibre) depuis un
script, un agent IA, ou votre propre interface.

> ⚠️ **Projet communautaire, non affilié à SFR.** Basé sur une
> rétro-ingénierie de l'interface web publique de la box (aucun binaire ni
> firmware n'a été désassemblé : uniquement le HTML/JS servi par la box
> elle-même). Le firmware peut changer à tout moment et casser ce dépôt —
> voir [Rétro-ingénierie](#rétro-ingénierie--comment-ça-marche) pour
> comprendre comment diagnostiquer et corriger ce cas.

## Pourquoi

L'interface web de cette box ne propose pas d'API JSON/REST documentée
(contrairement par exemple à la Freebox) : chaque page de configuration
est un formulaire HTML classique. Ce projet reproduit fidèlement le
protocole d'authentification et le remplissage de ces formulaires pour
exposer la configuration via trois interfaces :

- **CLI** (`sfrbox ...`) pour scripter/automatiser depuis un terminal.
- **API REST** (FastAPI) pour l'intégrer à votre propre outillage.
- **Serveur MCP** (`sfrbox-mcp`) pour la piloter depuis un agent IA
  (Claude Code, Claude Desktop, tout client MCP).

Les trois s'appuient sur la même bibliothèque Python (`src/sfrbox/`),
qui peut aussi être utilisée directement.

## Modèle de box concerné

Testé et validé contre une box avec bandeau `Version : NB6VAC-MAIN-R4.0.47hx`
(visible en bas de la page d'accueil `http://192.168.1.1/`, ou dans
`Etat > Général`). Il s'agit de la box fibre couramment appelée « SFR Box
7 », basée sur une plateforme Sagemcom. **Non testé** sur la SFR Box 8 ni
sur les anciennes box ADSL (NB4/NB6 non-VAC) : l'authentification par
challenge est probablement proche, mais les formulaires de configuration
peuvent différer. Contributions/rapports bienvenus (voir
[Contribuer](#contribuer)).

## Installation

Nécessite [`uv`](https://docs.astral.sh/uv/) et Python ≥ 3.11.

```bash
git clone <ce-dépôt>
cd sfr-box-toolkit
uv sync
cp .env.example .env
# éditez .env : SFRBOX_HOST (192.168.1.1 par défaut), SFRBOX_LOGIN (admin
# par défaut), SFRBOX_PASSWORD (le mot de passe imprimé sous votre box,
# ou celui que vous avez personnalisé dans Maintenance > Administration).
```

`.env` est listé dans `.gitignore` : vos identifiants ne doivent **jamais**
être commités. Ne les passez pas non plus en argument de ligne de commande
(ils resteraient dans l'historique du shell) : toutes les interfaces les
lisent depuis l'environnement/`.env`.

## Utilisation

### CLI

```bash
uv run sfrbox status wan
uv run sfrbox status devices
uv run sfrbox wifi status
uv run sfrbox wifi set-2g --ssid "MonReseau" --hidden false
uv run sfrbox wifi wpa-key-set 5ghz          # demande la nouvelle clé de façon masquée
uv run sfrbox dhcp status
uv run sfrbox nat portforward-add "Serveur web" --protocol tcp \
    --external-port 8080 --destination-ip-last-octet 42 --destination-port 80
uv run sfrbox --help                          # liste complète des commandes
```

### API REST

```bash
uv run uvicorn sfrbox.api:app --host 0.0.0.0 --port 8000
curl http://localhost:8000/wifi/status
curl -X POST http://localhost:8000/wifi/2ghz -H 'content-type: application/json' \
    -d '{"ssid": "MonReseau"}'
```

Documentation interactive auto-générée sur `http://localhost:8000/docs`.

⚠️ Cette API donne un accès complet à la configuration de la box (y
compris la lecture des clés Wifi en clair) à quiconque peut l'atteindre.
Ne l'exposez pas au-delà de votre réseau local sans ajouter votre propre
couche d'authentification.

### Serveur MCP

```bash
uv run sfrbox-mcp
```

Exemple de configuration client MCP (`~/.claude/mcp.json` ou équivalent) :

```json
{
  "mcpServers": {
    "sfrbox": {
      "command": "uv",
      "args": ["--directory", "/chemin/vers/sfr-box-toolkit", "run", "sfrbox-mcp"]
    }
  }
}
```

Le serveur lit `.env` depuis son répertoire de travail au démarrage. 21
outils sont exposés (statut, Wifi, DHCP, NAT/port forwarding, DMZ, UPnP,
pare-feu, DDNS, redémarrage...).

## Fonctionnalités couvertes

| Domaine | Lecture | Écriture |
|---|---|---|
| Statut WAN / équipements connectés | ✅ | — |
| Wifi (2,4 GHz / 5 GHz / invité) : actif, SSID, masqué | ✅ | ✅ |
| Clé WPA (2,4 GHz / 5 GHz / invité) | ✅ | ✅ |
| WPS | — | ✅ |
| DHCP (plage, bail, réservations statiques) | ✅ | ✅ |
| Redirections de ports (NAT) | — | ✅ (ajout/suppression) |
| DMZ | ✅ | ✅ |
| UPnP | ✅ | ✅ |
| Wake-on-LAN | ✅ | ✅ |
| Passthrough SIP ALG / PPTP / GRE | ✅ | ✅ |
| Pare-feu (règles simples) | ✅ | ✅ |
| DNS dynamique | ✅ | ✅ |
| Redémarrage / réinitialisation d'usine | — | ✅ (confirmation requise) |

Non couvert pour l'instant : téléphonie (VoIP), partage USB/Samba/UPnP-AV,
IPv6, planification Wifi horaire, mode Eco, routes statiques, entrées DNS
locales. Le code est structuré pour que l'ajout d'un module suive
exactement le même schéma que les modules existants (voir
[Contribuer](#contribuer)).

## Rétro-ingénierie : comment ça marche

Toute la logique vient de la lecture du HTML/JS **servis publiquement
par la box elle-même** (`/js/global.js`, `/js/login.js`, et les pages de
configuration) — aucune inspection de binaire, aucun accès à du code
propriétaire non exposé.

### Authentification (`src/sfrbox/auth.py`)

1. `POST /login` avec `action=challenge` (en-têtes `X-Requested-With:
   XMLHttpRequest` requis) renvoie un challenge aléatoire en XML :
   `<rsp stat="ok"><challenge>...</challenge></rsp>`.
2. Le client calcule :

   ```text
   hash = HMAC-SHA256(clé=challenge, message=SHA256_hex(login))
        + HMAC-SHA256(clé=challenge, message=SHA256_hex(password))
   ```

   Point non intuitif (source d'erreur n°1 si vous réimplémentez ceci) :
   c'est le **challenge qui sert de clé HMAC**, et le SHA-256 hexadécimal
   de l'identifiant/mot de passe qui sert de message — pas l'inverse.
   Le mot de passe en clair ne quitte jamais le client.
3. `POST /login` avec `method=passwd`, `zsid=<challenge>`,
   `hash=<hash>`, et les champs `login`/`password` vides. En cas de
   succès, la box pose un cookie de session `sid` et redirige vers
   `/index`.

### Pages de configuration (`src/sfrbox/parsing.py`, `client.py`)

Aucune API JSON : chaque page (`/wifi/config`, `/network/dhcp`,
`/network/nat`, ...) est un formulaire HTML classique en
Post/Redirect/Get. Ce toolkit :

1. Récupère la page et parse tous ses `<form>` (valeurs par défaut :
   texte/hidden tels quels, `checked` pour les radios/checkbox,
   `selected` pour les select).
2. Fusionne les champs demandés par l'appelant avec les valeurs par
   défaut (pour ne pas écraser involontairement un champ voisin, comme
   le ferait un navigateur qui ne soumet que les champs présents dans le
   formulaire affiché).
3. Ajoute le nom du bouton de soumission cliqué (une page peut contenir
   plusieurs formulaires, ou un même formulaire plusieurs boutons —
   observé sur `/wifi/config` où les blocs 2,4 GHz / 5 GHz / invité
   partagent un seul `<form>` avec 3 boutons distincts).
4. Soumet en `POST application/x-www-form-urlencoded`.

### Si le firmware change

Si une commande échoue avec `SFRBoxFormError`, c'est probablement que la
structure d'un formulaire a changé entre versions de firmware. Pour
diagnostiquer :

```bash
uv run python -c "
from sfrbox.config import Settings
from sfrbox.parsing import parse_forms
c = Settings.from_env().client(); c.login()
html = c.get_html('/wifi/config')
for f in parse_forms(html, c.base_url, '/wifi/config'):
    print(f.element_id, f.fields, f.buttons)
"
```

et comparez avec le module concerné dans `src/sfrbox/modules/`.

## Sécurité

- Toutes les requêtes se font en HTTP **non chiffré** sur le réseau
  local (comme le fait l'interface web native de la box) : c'est le
  comportement natif de la box, pas une régression de ce projet.
  N'utilisez ce toolkit que depuis un réseau de confiance.
- Ne commitez jamais `.env`, un export de configuration de la box, ou
  une capture HAR/PCAP : ces fichiers contiennent des identifiants et
  des clés Wifi en clair.
- L'API REST et le serveur MCP donnent tous deux un accès complet
  (lecture **et** écriture, y compris les clés Wifi) à la configuration
  de la box. Ne les exposez pas au-delà de votre réseau local.
- Les actions de redémarrage/réinitialisation d'usine exigent une
  confirmation explicite (`--yes` en CLI, `confirm=true` en API/MCP)
  pour limiter les déclenchements accidentels.

## Contribuer

Pour ajouter un module (ex. VoIP, IPv6) :

1. Authentifiez-vous sur l'interface web réelle et identifiez la page
   concernée.
2. Utilisez le script de diagnostic ci-dessus (section
   [Rétro-ingénierie](#rétro-ingénierie--comment-ça-marche)) pour lister
   les formulaires, champs, boutons de soumission de la page.
3. Créez `src/sfrbox/modules/<domaine>.py` sur le modèle des modules
   existants (`get_config`/`set_config` utilisant
   `client.get_form`/`client.submit_form`).
4. Exposez les nouvelles fonctions dans `cli.py`, `api.py` et
   `mcp_server.py`.
5. Ajoutez des tests sur la logique de parsing avec du HTML de test
   **synthétique** (voir `tests/test_parsing.py`) — ne commitez jamais
   de HTML exporté depuis une vraie box (il peut contenir des secrets).

## Licence

MIT — voir [LICENSE](LICENSE).

TDQS

B3/5.0

Scored across 21 tools

Disambiguation4/5

Most tools target distinct resources and actions (e.g., wifi_set_2ghz vs wifi_set_wpa_key, nat_get_dmz vs nat_set_dmz). A few adjacent wifi and NAT operations could be confused at first glance, but descriptions clarify their boundaries.

Naming Consistency5/5

All tool names use consistent snake_case with a clear domain prefix followed by an action or status suffix. The pattern is predictable across wan, wifi, dhcp, ddns, nat, firewall, and system tools.

Tool Count4/5

21 tools is somewhat heavy but reasonable for a router administration server covering Wi-Fi, DHCP, DDNS, NAT, firewall, status, and reboot. Each tool maps to a specific configuration or status operation.

Completeness3/5

Core router management areas are covered, but there is no tool to list existing NAT port forwards, despite nat_remove_port_forward requiring a line number. This creates a notable dead end for discovery and safe removal.

Maintenance

ActivityMaintained
ResponsivenessNo issues