Status : IMPLEMENTEE
| Severite | Nombre |
|---|---|
| BUG CERTAIN | 7 |
| BUG PROBABLE | 8 |
| RISQUE THEORIQUE | 9 |
| Total findings | 24 |
Les problemes les plus graves concernent l'exposition massive de credentials en clair dans le HTML genere (secrets.html, endpoints.html) qui est pousse vers O2switch (serveur public), des mots de passe Redis et des JWT Supabase hardcodes dans collect.sh, et l'absence d'echappement des donnees SSH avant injection dans le JSON et le HTML.
/stock_8to/33800-stack/monitoring/generate.d/04-secrets.sh:112-199 + /stock_8to/33800-stack/monitoring/generate.d/05-endpoints.sh:162-232secrets.html et endpoints.html contiennent en clair : mots de passe (MyUlia75$w, ntfy33800, 33800admin, d9fx-RNEz-xfK(, aE1RmUsd198cTt, oIoIlGGh6t4CDBLT), tokens API (glpat-yaowLwWBJhXfzJEC8UBC, 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, ptr_8B9+nxAyk...), cles Mailjet (0e24bea8cb588b742cccf75b34a253b1 / 3cbedc00ad58e9660585af9a5b73767d). Ces fichiers HTML sont generes puis pousses via git push o2switch main (generate.sh:4912). Le .gitignore n'exclut PAS les fichiers *.html. Le dashboard est accessible a https://dashboard.nowhere84.com sur Internet.# generate.sh:4910-4912
git add -A 2>/dev/null
git commit -m "Update: $(date +%Y-%m-%d_%H:%M)" 2>/dev/null
git push o2switch main 2>/dev/null &
<!-- 04-secrets.sh:129 -->
<td><code>MyUlia75$w</code></td>
<!-- 04-secrets.sh:177 -->
<tr><td>Token</td><td><code>glpat-yaowLwWBJhXfzJEC8UBC</code></td></tr>
https://dashboard.nowhere84.com/secrets.html ou endpoints.html obtient un acces complet a l'infrastructure./stock_8to/33800-stack/monitoring/.git/config:10http://oauth2:glpat-yaowLwWBJhXfzJEC8UBC@192.168.1.196/gouroubleu/dashboard-33800.git. Ce fichier est dans le repertoire .git qui n'est pas pousse, mais il reste un risque de fuite si le dossier est copie ou sauvegarde.[remote "origin"]
url = http://oauth2:glpat-yaowLwWBJhXfzJEC8UBC@192.168.1.196/gouroubleu/dashboard-33800.git
/stock_8to/33800-stack/monitoring/collect.sh:480-501urefsLoXibTZ36mtuygcLyNuqzOmIVkF) et DEV (rYfXNQRrVef2LXp8E1B06o4jiT5f5bxs) sont hardcodes en clair dans le script. Ce script est versionne dans le repo git (pousse vers GitLab et O2switch).REDIS_PROD_PING=$(ssh gouroubleu@192.168.1.12 'docker exec redis-prod redis-cli -a "urefsLoXibTZ36mtuygcLyNuqzOmIVkF" PING 2>/dev/null' | tr -d '\r\n')
REDIS_DEV_PING=$(ssh_cmd 192.168.1.51 "docker exec redis-dev redis-cli -a 'rYfXNQRrVef2LXp8E1B06o4jiT5f5bxs' PING 2>/dev/null" | tr -d '\r\n')
.env exclu du git, ou depuis les variables d'environnement./stock_8to/33800-stack/monitoring/collect.sh:517,533SUPABASE_PROD_API=$(curl -s ... -H "apikey: eyJhbGciOiJIUzI1NiIs..." 2>/dev/null)
/stock_8to/33800-stack/monitoring/collect-gitlab.sh:15glpat-yaowLwWBJhXfzJEC8UBC est hardcode en clair dans le script de collecte GitLab. Ce script est versionne dans le repo git.GITLAB_TOKEN="glpat-yaowLwWBJhXfzJEC8UBC"
/stock_8to/33800-stack/monitoring/collect.d/_common.sh:39,46 + /stock_8to/33800-stack/monitoring/collect.sh:52ssh_cmd, ssh_root) utilisent -o StrictHostKeyChecking=no. En cas de compromission DNS ou ARP spoofing sur le reseau local, un attaquant peut intercepter les connexions SSH (attaque MITM) et capturer les commandes executees (incluant les credentials Redis hardcodes).ssh_cmd() {
local host=$1
shift
timeout $SSH_TIMEOUT ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes gouroubleu@$host "$@" 2>/dev/null || echo ""
}
StrictHostKeyChecking=accept-new au lieu de no, et maintenir un fichier known_hosts pre-rempli./stock_8to/33800-stack/monitoring/collect.sh:985-1029vms_to_json, containers_to_json et sites_to_json construisent du JSON par concatenation de strings sans echapper les caracteres speciaux JSON (guillemets, backslashes, etc.). Le champ status est partiellement echappe avec sed 's/"/\\"/g', mais les champs name, target, restrict, auth ne sont pas du tout echappes. Si un nom de container ou un nom de site nginx contient un guillemet, le JSON sera invalide.
containers_to_json() {
local containers=$1
# ...
status=$(echo "$status" | sed 's/"/\\"/g')
# name et port ne sont PAS echappes
result+="{\"name\":\"$name\",\"status\":\"$status\",\"port\":\"$port\"}"
}
sites_to_json() {
result+="{\"name\":\"$name\",\"target\":\"$target\",\"access\":\"$restrict\",\"auth\":\"$auth\"}"
}
- **Impact** : Si un nom de container Docker ou un nom de site nginx contient un guillemet ou un backslash, le fichier current.json devient du JSON invalide, ce qui casse tout le dashboard. Ce cas peut se produire si un container est deploye avec un nom atypique.
- **Fix suggere** : Utiliser `jq` pour construire le JSON au lieu de la concatenation manuelle, ou echapper tous les champs avec une fonction dediee.
---
### F-08 : IP PVE inconsistante dans config.json vs code reel
- **Severite** : BUG PROBABLE
- **Fichier** : `/stock_8to/33800-stack/monitoring/config.json:4-5`
- **Description** : config.json declare `"ip": "192.168.1.10"` pour PVE, avec un champ additionnel `"ip_main": "192.168.1.4"`. Or, dans CLAUDE.md la machine PVE est referencee a `192.168.1.4`. Tous les scripts de collecte (collect.sh, collect.d/01-pve.sh) utilisent `192.168.1.10` en dur. Si PVE a deux IPs (une principale 192.168.1.4 et une autre 192.168.1.10), la distinction `ip` vs `ip_main` dans config.json pretent a confusion. De plus, `get_host_ip("pve")` retournerait `192.168.1.10` alors que la "vraie" IP principale est .4.
- **Code concerne** :
```json
"pve": {
"ip": "192.168.1.10",
"ip_main": "192.168.1.4",
get_host_ip("pve"), il obtiendra .10 au lieu de .4. Les scripts collect.d sont hardcodes sur .10 donc ils fonctionnent, mais la semantique de config.json est ambigue./stock_8to/33800-stack/monitoring/config.json:161"browser-connector": {"port": 5401} alors que CLAUDE.md (section "Ports services") indique explicitement : "browser-connector: port 5404 (pas 5401)". Cependant, le script collect.d/09-linkedin.sh utilise le port 5401 (BROWSER_PORT=5401) et semble fonctionner."browser-connector": {
"host": "prod-portainer",
"port": 5401,
/stock_8to/33800-stack/monitoring/config.json:155"connectors-api": {"port": 5400} alors que CLAUDE.md indique "connectors-api: port 5403 (derriere wireguard sidecar, pas 5400)". Selon le contexte, 5400 est le port interne (dans le container) et 5403 est le port expose/route via wireguard."connectors-api": {
"host": "prod-portainer",
"port": 5400,
external_port.set -e et masque les erreurs avec 2>/dev/null/stock_8to/33800-stack/monitoring/collect.sh:62>/dev/null || echo "". Cela signifie que si une commande SSH retourne une erreur (auth failure, timeout), le script continue avec des valeurs vides ou "0" sans signaler le probleme. Combine avec l'absence de validation des valeurs avant insertion dans le JSON, cela produit silencieusement un JSON avec des valeurs manquantes ou invalides.# Note: set -e retiré car le script gère ses propres erreurs
# ...
ssh_cmd() {
timeout $SSH_TIMEOUT ssh ... "$@" 2>/dev/null || echo ""
}
2>/dev/null./stock_8to/33800-stack/monitoring/collect.d/_common.sh:37-39ssh_cmd(), la variable $host n'est pas quotee dans l'appel SSH : gouroubleu@$host. Si $host contient des espaces ou caracteres speciaux (improbable pour des IPs mais possible pour des hostnames), cela causerait une erreur. De meme, check_ping ne quote pas $host.ssh_cmd() {
local host=$1
shift
timeout $SSH_TIMEOUT ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes gouroubleu@$host "$@" 2>/dev/null || echo ""
}
gouroubleu@"$host".git add -A dans generate.sh pousse tout vers O2switch/stock_8to/33800-stack/monitoring/generate.sh:4910git add -A ajoute TOUS les fichiers non-gitignored au commit, incluant potentiellement des fichiers sensibles crees entre deux commits (ex: des fichiers .json temporaires dans data/, des fichiers de collecte). Le .gitignore est minimal et n'exclut que current.json (racine), history/*.json, logs/, archives/, downloads/. Les fichiers .html generes (dont secrets.html) sont pousses.git add -A 2>/dev/null
git commit -m "Update: $(date +%Y-%m-%d_%H:%M)" 2>/dev/null
git push o2switch main 2>/dev/null &
git add -A par git add explicite des fichiers attendus, ou enrichir le .gitignore pour exclure les fichiers sensibles./stock_8to/33800-stack/monitoring/services-registry.json (permissions: -rw------- root root)services-registry.json a les permissions 600 et est owned par root, alors que le script generate.sh s'execute en tant que gouroubleu. La commande chown -R gouroubleu:gouroubleu *.json data/ (generate.sh:4909) tente de corriger les permissions mais chown sur un fichier owned par root necessite sudo. Le || true masque l'erreur.chown -R gouroubleu:gouroubleu *.json data/ 2>/dev/null || true
services-registry.json ne peut pas etre lu par le script generate.sh (owner root, mode 600). Si un module de generation en a besoin, il echouera silencieusement.sudo chown gouroubleu:gouroubleu services-registry.json) ou s'assurer que generate.sh tourne avec les privileges adequats./stock_8to/33800-stack/monitoring/collect.sh:32 + /stock_8to/33800-stack/monitoring/collect.d/_common.sh:16/tmp/monitoring-buffer est cree et rempli a chaque execution de collect.sh, mais il n'est jamais nettoye. Les fichiers JSON dans ce buffer contiennent des donnees d'infrastructure (etats ZFS, statistiques). Le trap en ligne 20 ne nettoie que le lockfile, pas le buffer.BUFFER_DIR="/tmp/monitoring-buffer"
mkdir -p "$BUFFER_DIR" "$MONITORING_DIR/history" "$LOGS_DIR"
# ... aucun cleanup de BUFFER_DIR
trap "rm -f $LOCKFILE" EXIT
rm -rf "$BUFFER_DIR" au trap EXIT./stock_8to/33800-stack/monitoring/collect.sh:480-485ssh gouroubleu@192.168.1.12 sans ssh_cmd (pas de timeout, pas de BatchMode, pas de ConnectTimeout). Si le serveur est lent a repondre, le script peut bloquer indefiniment.REDIS_PROD_PING=$(ssh gouroubleu@192.168.1.12 'docker exec redis-prod redis-cli -a "urefsLoXibTZ36mtuygcLyNuqzOmIVkF" PING 2>/dev/null' | tr -d '\r\n')
ssh_cmd 192.168.1.12 a la place de ssh gouroubleu@192.168.1.12./stock_8to/33800-stack/monitoring/assets/data-loader.js:28,86data-loader.js utilise innerHTML pour inserer des donnees dans le DOM : (1) le message d'erreur (ligne 28) inclut error.message dans du HTML via template literal, (2) statusBadge() (ligne 86) insere la valeur de status directement dans du HTML. Si current.json contient des donnees malformees (ex: un statut qui contient du HTML), cela pourrait mener a du XSS.errorDiv.innerHTML = `<span style="color: var(--red);">Erreur chargement données: ${error.message}</span>`;
// ...
function statusBadge(status) {
return `<span class="status status-${color}">${status}</span>`;
}
textContent au lieu de innerHTML, ou echapper les donnees avant insertion./stock_8to/33800-stack/monitoring/generate.sh (4935 lignes) + /stock_8to/33800-stack/monitoring/generate.d/*.shtemplates.sh (html_head/html_header/html_footer), generate.d/_common.sh (gen_html_head/gen_header/gen_footer), et les fonctions inline de generate.sh (generate_html_head/generate_header/generate_footer). Les navigations ne sont pas synchronisees entre ces 3 systemes./stock_8to/33800-stack/monitoring/generate.sh:4912git push o2switch main 2>/dev/null & est execute en arriere-plan. Si une nouvelle execution de generate.sh demarre avant que le push ne soit termine, le prochain git add -A && git commit pourrait entrer en conflit avec le push en cours (git lock file).git push o2switch main 2>/dev/null &
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Push O2switch en background"
2>/dev/null masque l'erreur./stock_8to/33800-stack/monitoring/collect.sh:866-888collect_container_schedulers itere sur les noms de containers Docker et les utilise directement dans des commandes SSH (docker cp $container:...). Si un nom de container contient des caracteres speciaux shell (espace, point-virgule, backtick), cela pourrait mener a une injection de commande.for container in $containers; do
local manifest=$(ssh_cmd "$host" "docker cp $container:/app/scheduler.json ...")
$container dans les commandes SSH./stock_8to/33800-stack/monitoring/generate.sh:4929-4932NOTIF_TITLE ou NOTIF_BODY contient des guillemets, le JSON sera invalide et la notification echouera.
NOTIF_TITLE="Dashboard généré ✓"
NOTIF_BODY="${PAGES_COUNT} pages - ${TIMESTAMP}
https://dashboard.nowhere84.com"
curl -s -X POST "http://192.168.1.12:5300/api/notify/push" \ -H "Content-Type: application/json" \ -d "{\"title\": \"${NOTIF_TITLE}\", \"body\": \"${NOTIF_BODY}\", \"level\": \"info\"}"
- **Impact** : Le newline dans NOTIF_BODY casse le JSON. La notification echoue probablement a chaque execution. Le `|| echo` masque l'erreur.
- **Fix suggere** : Utiliser `jq` pour construire le JSON, ou echapper les newlines.
---
### F-22 : audit-notify.sh - Injection JSON dans le body de notification
- **Severite** : RISQUE THEORIQUE
- **Fichier** : `/stock_8to/33800-stack/monitoring/audit-notify.sh:59-62`
- **Description** : Le body de notification inclut la variable `$BODY` qui contient des backslash-n et des details extraits de la sortie de l'audit. Ces details ne sont pas echappes pour JSON, ce qui peut casser le JSON si les messages d'audit contiennent des guillemets.
- **Code concerne** :
```bash
BODY="Score: $PASSED/$TOTAL checks OK\nCritical: $CRITICAL\nWarning: $WARNING\n\nDetails:\n$DETAILS"
curl -s -X POST "$NOTIF_URL" \
-H "Content-Type: application/json" \
-d "{\"title\": \"$TITLE\", \"body\": \"$BODY\", \"level\": \"$LEVEL\"}"
jq -n --arg body "$BODY" --arg title "$TITLE" '{title: $title, body: $body, level: "warning"}'./stock_8to/33800-stack/monitoring/config.json:47-51vscode a l'IP 192.168.1.154, mais cette machine n'apparait pas dans les scripts de collecte (pas de collect.d module pour vscode) et n'est pas referencee dans CLAUDE.md comme une VM active. Le label "VSCode" fait penser a l'editeur web (Coder), qui est normalement un service Docker, pas une VM dediee."vscode": {
"ip": "192.168.1.154",
"mdns": "vscode.local",
"label": "VSCode"
},
/stock_8to/33800-stack/monitoring/collect.sh:10-20/tmp/collect-monitoring.lock et verifie avec kill -0. Si le processus d'origine se termine et qu'un nouveau processus (sans rapport) recoit le meme PID, la verification kill -0 reussira et le lock sera considere comme actif alors qu'il ne l'est plus. De plus, entre la verification et la creation du lockfile, il y a une race condition (TOCTOU).LOCKFILE="/tmp/collect-monitoring.lock"
if [ -f "$LOCKFILE" ]; then
LOCK_PID=$(cat "$LOCKFILE" 2>/dev/null)
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
echo "[$(date)] Collecte déjà en cours (PID $LOCK_PID), abandon."
exit 0
fi
rm -f "$LOCKFILE"
fi
echo $$ > "$LOCKFILE"
flock au lieu d'un lockfile PID.git add -A pousse tout sans filtragegit push o2switch main vers un bare repo sur O2switch. Le transport utilise une connexion SSH (o2switch:repositories/dashboard.git). Le transfert est securise par SSH. Cependant, le contenu pousse inclut secrets.html et endpoints.html contenant tous les credentials en clair (cf. F-01).