33800 Docs

← Retour

AUDIT PASSE 2 - Dashboard 33800

Angle : Securite Bash, injection de commandes, gestion d'erreurs, robustesse SSH

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


Table des matieres

  1. Synthese executive
  2. Fichiers audites
  3. Findings critiques
  4. Findings importants
  5. Findings moderees
  6. Findings mineures
  7. Recapitulatif par classification

1. Synthese executive

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.


2. Fichiers audites

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

3. Findings critiques

F01 - BUG CERTAIN - Credentials en clair deployes sur site public

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.


F02 - BUG CERTAIN - Credentials dupliques dans endpoints.html

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.


F03 - BUG CERTAIN - Contenu de fichiers markdown injecte dans HTML sans echappement complet

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/</\&lt;/g' | sed 's/>/\&gt;/g' | tr '\n' ' ' | cut -c1-150)

# Ligne 237 - contenu COMPLET du fichier avec meme echappement partiel
<div class="file-content">$(cat "$file" | sed 's/</\&lt;/g' | sed 's/>/\&gt;/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)


4. Findings importants

F04 - BUG PROBABLE - StrictHostKeyChecking=no global

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.


F05 - BUG PROBABLE - Absence totale de set -e dans tous les scripts

Fichiers : TOUS les .sh

Aucun des 23 scripts ne contient set -e, set -o pipefail ou set -u. Les consequences :

Exemples concrets :

Classification : 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.


F06 - BUG PROBABLE - Donnees SSH/curl injectees dans HTML sans echappement

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/</\&lt;/g; s/>/\&gt;/g'

Ce sed ne traite que < et >. Il manque :

Un log contenant &amp; 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.


F07 - BUG PROBABLE - Variables non quotees dans les heredocs non-quoted

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 :

Classification : 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.


F08 - RISQUE THEORIQUE - Injection jq dans get_config

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.


F09 - BUG PROBABLE - IP hardcodee dans 19-ai-orchestrator.sh et 20-ai-jobs.sh

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.


F10 - BUG PROBABLE - Utilisation de sed -i avec regex sur contenu genere

Fichier : 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.


5. Findings moderees

F11 - RISQUE THEORIQUE - inject_json injecte le JSON brut dans le HTML

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 :

Le 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.


F12 - RISQUE THEORIQUE - SSH sans timeout uniforme

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.


F13 - RISQUE THEORIQUE - Arithmetic comparison sans validation numerique

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".


F14 - RISQUE THEORIQUE - Variable non quotee dans echo/tr

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.


F15 - RISQUE THEORIQUE - Donnees API curl non validees

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.


F16 - RISQUE THEORIQUE - IP:port hardcode dans les exemples API (deploy public)

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.


F17 - RISQUE THEORIQUE - Globbing non protege dans les boucles for

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.


6. Findings mineures

F18 - FAUX POSITIF - UUOC (Useless Use of Cat)

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.


F19 - FAUX POSITIF - deploy_page ne verifie pas le resultat du cp

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.


F20 - FAUX POSITIF - generate.sh ligne 57 : $title non echappe

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.


F21 - RISQUE THEORIQUE - Execution directe au source dans 09-architecture.sh

Fichier : 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.


F22 - RISQUE THEORIQUE - XSS dans JavaScript client (20-ai-jobs.sh)

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.


F23 - RISQUE THEORIQUE - base64 decode dans 25-services.sh

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.


F24 - RISQUE THEORIQUE - Pas de nettoyage des fichiers temporaires

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.


7. Recapitulatif

Par classification

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

Par priorite de correction

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

Synthese des actions cles

  1. SUPPRIMER les credentials en clair de 04-secrets.sh et 05-endpoints.sh (ou proteger par authentification)
  2. AJOUTER set -euo pipefail en tete de tous les scripts
  3. CREER une fonction html_escape() dans _common.sh qui echappe &, <, >, ", '
  4. UTILISER des heredocs quotes (<< 'DELIM') partout ou les variables ne sont pas necessaires
  5. REMPLACER StrictHostKeyChecking=no par StrictHostKeyChecking=accept-new (accepte au premier contact, rejette les changements)
  6. NETTOYER le repertoire /tmp/dashboard-33800 a la fin de generate.sh
  7. CENTRALISER les ports dans config.json et supprimer les IP hardcodees

Fin de l'audit PASSE 2 Genere par Claude Opus 4.6 - 15/02/2026