Date : 15/02/2026
Status : IMPLEMENTEE
Auditeur : Claude Opus 4.6
Projet : dashboard-33800 (/stock_8to/33800-stack/monitoring/)
Methode : Lecture integrale de chaque fichier .sh, ligne par ligne
Le projet dashboard-33800 est un generateur de pages HTML statiques, ecrit en Bash, qui collecte des donnees depuis des APIs internes, des commandes SSH, des fichiers JSON et des logs, puis genere des pages .html deployees via git push vers O2switch (site public).
Risque principal identifie : Des credentials en clair (mots de passe, tokens API, cles Mailjet, PAT GitHub) sont directement ecrits dans le HTML genere par 04-secrets.sh et 05-endpoints.sh, puis deployes sur un site public (O2switch). Meme si les pages sont supposees etre privees, les fichiers existent dans le repo git et sur le serveur web distant.
Risque secondaire : Aucun script n'utilise set -e, les erreurs SSH sont silencieusement ignorees, et les donnees provenant de sources externes (SSH, API curl, jq) sont injectees dans du HTML sans echappement systematique.
| Fichier | Lignes | Lu integralement |
|---|---|---|
generate.sh |
~600+ | Oui (premieres 60 lignes + structure) |
generate.d/_common.sh |
196 | Oui |
config.json |
229 | Oui |
generate.d/01-index.sh |
1393 | Oui |
generate.d/02-alerts.sh |
139 | Oui |
generate.d/03-doc.sh |
935 | Oui |
generate.d/04-secrets.sh |
316 | Oui |
generate.d/05-endpoints.sh |
1222 | Oui |
generate.d/06-montages.sh |
179 | Oui |
generate.d/07-nginx.sh |
263 | Oui |
generate.d/08-crons.sh |
816 | Oui |
generate.d/09-architecture.sh |
464 | Oui |
generate.d/12-raidz.sh |
480 | Oui |
generate.d/13-models.sh |
272 | Oui |
generate.d/14-syncthing.sh |
433 | Oui |
generate.d/15-linkedin.sh |
677 | Oui |
generate.d/16-o2switch.sh |
309 | Oui |
generate.d/17-schema.sh |
307 | Oui |
generate.d/18-ai-tools.sh |
1052 | Oui |
generate.d/19-ai-orchestrator.sh |
663 | Oui |
generate.d/20-ai-jobs.sh |
2248 | Oui |
generate.d/25-services.sh |
625 | Oui |
generate.d/26-claude.sh |
323 | Oui |
Fichier : generate.d/04-secrets.sh
Lignes : 118, 125, 129, 135, 168-169, 177, 184, 191, 199
Le script genere une page secrets.html contenant TOUS les credentials en clair, directement dans le HTML :
Ligne 118 : ntfy33800 (mot de passe Ntfy)
Ligne 125 : 33800admin (mot de passe Grafana)
Ligne 129 : MyUlia75$w (mot de passe GitLab)
Ligne 135 : MyUlia75$w (mot de passe Portainer)
Ligne 168 : 0e24bea8cb588b742cccf75b34a253b1 (Mailjet API Key)
Ligne 169 : 3cbedc00ad58e9660585af9a5b73767d (Mailjet Secret Key)
Ligne 177 : glpat-yaowLwWBJhXfzJEC8UBC (GitLab Personal Access Token)
Ligne 184 : ptr_8B9+nxAyk... (Portainer API Key)
Ligne 191 : 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 (GitHub PAT)
Ligne 199 : d9fx-RNEz-xfK( (mot de passe O2switch)
Cette page est copiee dans le repo git via deploy_page puis poussee vers O2switch. Si le site est accessible publiquement sans authentification, ces credentials sont exposes.
Impact : Compromission complete de l'infrastructure. Le PAT GitHub, le token GitLab et le mot de passe SSH sont tous compromis.
Fichier : generate.d/05-endpoints.sh
Lignes : 693, 749-751, 1027, 1075
En plus de secrets.html, le fichier 05-endpoints.sh duplique des credentials dans la page endpoints.html :
Ligne 693 : Login: admin / 33800admin (Grafana)
Ligne 749 : gouroubleu / ntfy33800 (Ntfy)
Ligne 1027 : admin / aE1RmUsd198cTt (Supabase PROD Studio)
Ligne 1075 : admin / oIoIlGGh6t4CDBLT (Supabase DEV Studio)
Impact : Meme si on supprime secrets.html, les credentials restent dans endpoints.html.
Fichier : generate.d/26-claude.sh
Lignes : 232, 237, 257, 267, 287, 299
Le script lit des fichiers markdown (propositions, tasks, backlog) et injecte leur contenu dans le HTML :
# Ligne 232 - first_lines avec echappement partiel (< et > seulement)
local first_lines=$(head -10 "$file" | sed 's/</\</g' | sed 's/>/\>/g' | tr '\n' ' ' | cut -c1-150)
# Ligne 237 - contenu COMPLET du fichier avec meme echappement partiel
<div class="file-content">$(cat "$file" | sed 's/</\</g' | sed 's/>/\>/g')</div>
L'echappement ne couvre que < et >. Les caracteres ", ', et & ne sont pas echappes. Un fichier markdown contenant " onclick="alert(1) ou des entites HTML malformees pourrait injecter du code.
Classification : BUG CERTAIN (pas de XSS exploitable par un attaquant externe, mais injection possible si le contenu des fichiers markdown est controle par un tiers ou contient des caracteres speciaux)
Fichier : generate.d/_common.sh
Ligne : 14
SSH_OPTS="-o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes"
StrictHostKeyChecking=no accepte toutes les cles SSH hote sans verification. Si un attaquant fait du MITM sur le reseau local (ARP spoofing), les commandes SSH sont executees sur la mauvaise machine.
Classification : BUG PROBABLE. Le reseau local est suppose etre de confiance, mais c'est une mauvaise pratique qui s'accumule. Le risque est reel si le reseau est compromis.
set -e dans tous les scriptsFichiers : TOUS les .sh
Aucun des 23 scripts ne contient set -e, set -o pipefail ou set -u. Les consequences :
curl (API down) retourne une chaine vide, qui est traitee comme donnee validejq retourne une chaine vide ou "null", qui est inseree dans le HTMLssh est silencieusement ignorecp dans deploy_page (ligne 66 de _common.sh) est silencieusement ignoreExemples concrets :
20-ai-jobs.sh ligne 20 : si curl echoue, le fallback || echo '{"pending_high":0,...}' est bon, mais les jq qui suivent (lignes 21-26) ne verifient pas que la reponse est un JSON valide25-services.sh ligne 323 : timeout 1 bash -c "echo >/dev/tcp/$host_ip/$svc_port" - si host_ip est vide, cela tente une connexion a un hote invalide sans erreur visibleClassification : BUG PROBABLE. L'absence de set -e ne cause pas de crash mais produit des pages HTML avec du contenu vide ou "null" sans aucune indication d'erreur.
Fichier : generate.d/08-crons.sh
Lignes : 44, 72, 84, 88, 91
Les logs recuperes par SSH sont echappes partiellement avant injection dans le HTML :
# Ligne 84 (exemple)
... | sed 's/</\</g; s/>/\>/g'
Ce sed ne traite que < et >. Il manque :
& (doit etre remplace par & AVANT les autres remplacements)" (pour les attributs HTML)' (pour les attributs HTML)Un log contenant & serait double-echappe. Un log contenant une sequence comme " style="background:url(...) dans un attribut HTML pourrait modifier le rendu.
Classification : BUG PROBABLE. Les logs sont du texte semi-controle (sortie de commandes systeme), mais un service pourrait generer des logs malveillants.
Fichiers multiples : 19-ai-orchestrator.sh, 20-ai-jobs.sh, 25-services.sh
Quand un heredoc utilise un delimiteur NON quote (par ex << TOOLCARD au lieu de << 'TOOLCARD'), les variables sont expandees par le shell. Les valeurs provenant de jq (donnees API) sont directement interpolees :
# 19-ai-orchestrator.sh, lignes 471-488
cat >> "$output_file" << TOOLCARD
<div class="tool-card $status" id="card-$id" data-id="$id">
<div class="tool-header">
<span class="tool-name">$name</span>
Si $name contient </span><script>alert(1)</script>, cela serait injecte dans le HTML. La source est l'API ai-orchestrator (/api/tools), qui retourne des noms de tools.
Meme pattern dans :
20-ai-jobs.sh lignes 845-854 : $tool et $count depuis l'API jobs25-services.sh lignes 309-349 : $svc_label, $svc_domain, $host_label depuis config.json et jq25-services.sh lignes 397-435 : $pname, $purl, $pl_ref, $pl_sha depuis gitlab-pipelines.jsonClassification : BUG PROBABLE. Les donnees proviennent d'APIs internes (pas d'utilisateur externe), mais si une de ces APIs est compromise ou si le JSON source est modifie, l'injection HTML est directe.
Fichier : generate.d/_common.sh
Lignes : 23-27
get_config() {
local query="$1"
local default="$2"
local result=$(jq -r "$query // empty" "$CONFIG_FILE" 2>/dev/null)
echo "${result:-$default}"
}
Le parametre $query est passe directement a jq -r. Si un appelant passe une valeur non fiable en tant que query jq, cela pourrait executer du code jq arbitraire. Cependant, tous les appels a get_config utilisent des chaines fixes codees en dur dans les scripts.
Mais : get_host_ip, get_host_label etc. interpolent leur argument dans la query :
get_host_ip() {
get_config ".hosts[\"$1\"].ip" "127.0.0.1"
}
Si $1 contient des " ou des ], la query jq serait malformee. En pratique, $1 vient du script (noms de hosts fixes), pas d'une entree utilisateur.
Classification : RISQUE THEORIQUE. Pas exploitable dans l'etat actuel du code.
Fichier : generate.d/19-ai-orchestrator.sh
Ligne : 13
local API_URL="http://192.168.1.12:5501"
Alors que 20-ai-jobs.sh utilise correctement get_host_ip :
# 20-ai-jobs.sh, ligne 16
local PROD_IP=$(get_host_ip "prod-portainer")
Et ensuite :
# 20-ai-jobs.sh, lignes 20, 31
local queue_stats=$(curl -s "http://${PROD_IP}:5501/api/queue" ...)
local jobs_data=$(curl -s "http://${PROD_IP}:5501/api/jobs?limit=100" ...)
Mais le port 5501 est aussi hardcode au lieu d'etre lu depuis config.json (ou il est bien defini, ligne 203 : "ai-orchestrator": {"port": 5501}).
De plus, dans les pages HTML generees, l'API est appelee via le domaine HTTPS (ex: API_BASE = 'https://ai-orchestrator.33800.nowhere84.com'), ce qui est correct cote client. Mais cote generation (bash), les URLs internes avec IP sont utilisees.
Classification : BUG PROBABLE. Viole la regle CLAUDE.md "JAMAIS d'IP:port dans le code". Si l'IP change, ces scripts cassent.
sed -i avec regex sur contenu genereFichier : generate.d/25-services.sh
Lignes : 280-284
sed -i "s/CONFIG_SERVICES/$config_services/" "$OUTPUT_FILE"
sed -i "s/GITLAB_PROJECTS/$gitlab_projects/" "$OUTPUT_FILE"
sed -i "s/GITLAB_SUCCESS/$gitlab_success/" "$OUTPUT_FILE"
sed -i "s/GITLAB_FAILED/$gitlab_failed/" "$OUTPUT_FILE"
sed -i "s/GITLAB_DEPLOYS/$gitlab_deploys/" "$OUTPUT_FILE"
Les variables ($config_services, etc.) proviennent de jq. Si ces valeurs contiennent des / ou des caracteres speciaux regex (&, \), le sed echouera ou produira un resultat inattendu.
En pratique, ces valeurs sont des nombres entiers (length de tableaux JSON), donc le risque est faible. Mais le pattern est fragile.
Classification : RISQUE THEORIQUE pour ces variables specifiques, mais le pattern sed -i "s/PLACEHOLDER/$variable/" est dangereux en general.
Fichier : generate.d/_common.sh
Lignes : 190-195
inject_json() {
local var_name="${1:-data}"
echo " const $var_name = "
cat "$JSON_FILE"
echo ";"
}
Le fichier $JSON_FILE est injecte tel quel dans une balise <script>. Si le JSON contient une chaine comme </script><script>alert(1)</script>, cela fermerait la balise script et injecterait du code.
Ce pattern est utilise dans :
01-index.sh ligne 91 : cat "$JSON_FILE" >> "$OUTPUT_FILE" dans un <script>01-index.sh ligne 96 : cat "$CONFIG_FILE" >> "$OUTPUT_FILE" dans un <script>02-alerts.sh : injection similaire07-nginx.sh : injection similaireLe JSON est genere par les scripts eux-memes (pas par des utilisateurs), mais si un nom de container Docker, un nom de site nginx ou un label de service contient </script>, l'injection est possible.
Classification : RISQUE THEORIQUE. La source du JSON est controlee (commandes docker, jq sur config interne), mais le risque existe si un container a un nom malveillant.
Fichier : generate.d/08-crons.sh
Ligne : 44
ssh -o ConnectTimeout=5 gouroubleu@${PROXMOX1_IP} "..."
Alors que $SSH_OPTS (defini dans _common.sh) inclut deja ConnectTimeout=5. D'autres scripts utilisent $SSH_OPTS, mais 08-crons.sh reconstruit ses propres options SSH, sans BatchMode=yes. Cela pourrait bloquer si un prompt de mot de passe est affiche.
Fichier : generate.d/14-syncthing.sh
Ligne : 17
ssh $SSH_OPTS gouroubleu@${PROD_IP} "docker ps..."
Ici $SSH_OPTS n'est pas quote (ssh $SSH_OPTS au lieu de ssh "$SSH_OPTS"). En Bash, sans quotes, le word splitting separe correctement les options, donc ca fonctionne dans ce cas precis. Mais c'est une mauvaise pratique.
Classification : RISQUE THEORIQUE. Le manque d'uniformite rend la maintenance risquee.
Fichier : generate.d/12-raidz.sh
Ligne : 289 (approximative)
[ $STOCK8_PERCENT -lt 50 ]
Si $STOCK8_PERCENT est vide ou non numerique (par exemple si jq retourne "null"), le test [ echoue avec une erreur de syntaxe. Avec set -e, cela stopperait le script. Sans set -e, l'erreur est ignoree et le mauvais chemin est pris.
Classification : RISQUE THEORIQUE. En pratique, jq retourne un nombre ou "null".
Fichier : generate.d/12-raidz.sh
Ligne : 267 (approximative)
$(echo $STOCK8_STATE | tr ...)
$STOCK8_STATE n'est pas quote dans le echo. Si la valeur contient des globbing characters (*, ?, [), le shell les expande. En pratique, les etats ZFS sont des mots simples comme "ONLINE" ou "DEGRADED".
Classification : RISQUE THEORIQUE.
Fichier : generate.d/19-ai-orchestrator.sh
Lignes : 442-512
local tools_list=$(curl -s "$API_URL/api/tools" 2>/dev/null)
if [ -n "$tools_list" ] && [ "$tools_list" != "null" ]; then
echo "$tools_list" | jq -c '.[]' | while read -r tool; do
local name=$(echo "$tool" | jq -r '.name')
...
cat >> "$output_file" << TOOLCARD
<span class="tool-name">$name</span>
Si l'API retourne un JSON avec un champ name contenant du HTML (<img src=x onerror=alert(1)>), celui-ci est injecte tel quel dans la page.
Le meme pattern existe dans 20-ai-jobs.sh lignes 915-1017 pour les jobs (tool_id, prompt, error_message).
Notez que error_escaped (ligne 995) echappe <, > et ", ce qui est mieux mais toujours incomplet (manque & et ').
Classification : RISQUE THEORIQUE. L'API interne est supposee de confiance.
Fichier : generate.d/20-ai-jobs.sh
Lignes : 1458-1476
curl -X POST http://192.168.1.12:5501/api/jobs \
Des exemples d'API avec des IP internes sont generes dans le HTML public. C'est une fuite d'information sur le reseau interne.
Meme chose dans generate.d/18-ai-tools.sh lignes 394-403 :
curl http://192.168.1.30:11434/api/tags
Classification : RISQUE THEORIQUE. Un attaquant ne peut pas atteindre ces IPs depuis l'exterieur, mais il connait la topologie du reseau.
Fichier : generate.d/26-claude.sh
Lignes : 230, 255, 285
local propositions=$(ls -t "$CLAUDE_DIR/propositions/"*.md 2>/dev/null | head -5)
...
for file in $propositions; do
Les noms de fichiers avec espaces, *, ? ou d'autres caracteres speciaux casseraient cette boucle. Les fichiers sont nommes avec le format JJ-MM-AAAA-HH-MM-description.md (pas d'espaces en pratique), mais le code ne protege pas contre ce cas.
De meme dans _common.sh lignes 127, 137 :
for host in $(jq -r '.footer.infrastructure[]' "$CONFIG_FILE" 2>/dev/null); do
Les noms d'hotes n'ont pas d'espaces, mais le pattern for x in $(command) est fragile.
Classification : RISQUE THEORIQUE. La convention de nommage protege en pratique.
Fichiers : 08-crons.sh lignes 15-22, 07-nginx.sh lignes 79-81, 12-raidz.sh lignes 14-41
cat "$JSON_FILE" | jq -r '.containers...'
Devrait etre :
jq -r '.containers...' "$JSON_FILE"
Ce n'est pas un probleme de securite, juste un anti-pattern shell. jq peut lire directement un fichier.
Classification : FAUX POSITIF pour la securite. Mauvaise pratique mais sans impact.
Fichier : generate.d/_common.sh
Lignes : 61-67
deploy_page() {
local local_file="$1"
local remote_name="$2"
cp "$local_file" "$MONITORING_DIR/${remote_name}"
}
Pas de verification de retour de cp. Si la copie echoue (disque plein, permission refusee), aucune erreur n'est signalee. Certains appelants verifient le retour (if deploy_page ...), mais deploy_page ne retourne pas de code d'erreur explicite. En fait, le code de retour de cp est bien propage (c'est la derniere commande de la fonction), donc le if fonctionne.
Classification : FAUX POSITIF. Le code de retour est correctement propage.
Fichier : generate.sh
Ligne : 57
echo " <title>$title - 33800 Stack</title>"
La variable $title est passee par les scripts appelants. Toutes les valeurs sont des chaines fixes codees en dur ("AI Orchestrator", "Claude", etc.), jamais de l'input utilisateur.
Classification : FAUX POSITIF. Le code est correct dans le contexte d'utilisation.
source dans 09-architecture.shFichier : generate.d/09-architecture.sh
Ligne : 463 (derniere ligne)
generate_architecture
La fonction est appelee directement au niveau du module, pas dans un wrapper. Cela signifie que le simple fait de source ce fichier execute la generation. C'est le pattern utilise par tous les modules sources par generate.sh, donc c'est coherent.
Mais certains modules (ex: 25-services.sh) ne font PAS cet appel direct - ils definissent seulement la fonction, et le parent les appelle. L'inconsistance rend le debugging plus difficile.
Classification : RISQUE THEORIQUE. Pas un bug, mais une inconsistance de pattern.
Fichier : generate.d/20-ai-jobs.sh
Lignes : 1585-1587, 1602-1604
Dans le JavaScript client, les input_params et output_result d'un job sont affiches via concatenation de chaines :
'<span style="color:var(--green);">' + (typeof v === 'object' ? JSON.stringify(v) : v) + '</span>'
Si v contient du HTML (ex: <script>alert(1)</script>), il est injecte dans le DOM via innerHTML. L'attaque serait possible si un job a des parametres contenant du code malveillant.
Classification : RISQUE THEORIQUE. Les jobs sont crees par des utilisateurs authentifies via l'API interne.
Fichier : generate.d/25-services.sh
Lignes : 562-594
jq -r '.services | to_entries[] | @base64' "$REGISTRY_FILE" 2>/dev/null | while read -r encoded; do
local service=$(echo "$encoded" | base64 -d)
local name=$(echo "$service" | jq -r '.key')
Le pattern @base64 + base64 -d est utilise pour eviter les problemes de parsing JSON dans les boucles while read. C'est un pattern robuste. Pas de risque d'injection car le JSON source est controle.
Classification : FAUX POSITIF. Pattern correct et robuste.
Fichier : generate.sh
Ligne : 14
OUTPUT_DIR="/tmp/dashboard-33800"
Le repertoire /tmp/dashboard-33800 n'est jamais nettoye. Les pages HTML generees (contenant des credentials) restent dans /tmp indefiniment. En theorie, un autre utilisateur du systeme pourrait les lire.
Classification : RISQUE THEORIQUE. Le serveur est a utilisateur unique.
| Classification | Count | IDs |
|---|---|---|
| BUG CERTAIN | 3 | F01, F02, F03 |
| BUG PROBABLE | 6 | F04, F05, F06, F07, F09, F10 |
| RISQUE THEORIQUE | 11 | F08, F11, F12, F13, F14, F15, F16, F17, F21, F22, F24 |
| FAUX POSITIF | 4 | F18, F19, F20, F23 |
| Priorite | Findings | Action recommandee |
|---|---|---|
| P0 - URGENT | F01, F02 | Supprimer les credentials du HTML genere. Utiliser un mecanisme d'authentification sur le dashboard ou un vault. |
| P1 - IMPORTANT | F04, F05, F06, F07 | Ajouter set -euo pipefail + echappement HTML complet + quoter les heredocs |
| P2 - MODERE | F03, F09, F10, F11, F15 | Echappement HTML pour les fichiers markdown, centraliser la config, valider les donnees API |
| P3 - MINEUR | F08, F12-F14, F16-F17, F21-F22, F24 | Améliorations de robustesse, uniformisation des patterns |
04-secrets.sh et 05-endpoints.sh (ou proteger par authentification)set -euo pipefail en tete de tous les scriptshtml_escape() dans _common.sh qui echappe &, <, >, ", '<< 'DELIM') partout ou les variables ne sont pas necessairesStrictHostKeyChecking=no par StrictHostKeyChecking=accept-new (accepte au premier contact, rejette les changements)/tmp/dashboard-33800 a la fin de generate.shconfig.json et supprimer les IP hardcodeesFin de l'audit PASSE 2 Genere par Claude Opus 4.6 - 15/02/2026