Date : 15/02/2026 Status : IMPLEMENTEE Verificateur : Claude Opus 4.6 Methode : Lecture du code source reel pour chaque finding, confrontation avec la description du rapport
Contexte important : Le dashboard O2switch (https://dashboard.nowhere84.com) est protege par restriction IP via .htaccess (82.65.119.221 et 82.67.42.47 uniquement). Il n'est donc PAS public. Cela reduit la severite des findings lies a l'exposition de credentials sur le "site public".
Verdict : CONFIRME, severite REDUITE (P1, pas P0)
Verification dans le code source :
/stock_8to/33800-stack/monitoring/generate.d/04-secrets.sh : Le fichier genere secrets.html avec en clair :ntfy33800 (mot de passe Ntfy)33800admin (mot de passe Grafana)MyUlia75$w (SSH/GitLab/Portainer)0e24bea8cb588b742cccf75b34a253b1 / 3cbedc00ad58e9660585af9a5b73767dglpat-yaowLwWBJhXfzJEC8UBC (Token GitLab)ptr_8B9+nxAykmBdCtlPX8F2chjc5ArF+6COyU8eVl+FgXY= (API Key Portainer)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 (GitHub PAT)d9fx-RNEz-xfK( (mot de passe O2switch)Le bug est REEL : credentials en clair dans du code versionne et pousse vers O2switch. MAIS la severite CRITIQUE (P0) est FAUSSE car le dashboard est protege par restriction IP, donc PAS public. Severite reelle : HAUTE (P1) - mauvaise pratique (credentials dans git history) mais pas d'exposition publique.
Verdict : CONFIRME
Verification dans le code source - J'ai identifie ces systemes de navigation distincts :
generate.d/_common.sh gen_header() (ligne 88-113) : 15 liens (Dashboard, Alertes, Liens, Montages, Nginx, API, Archi, Doc, Modeles, Secrets, Crons, RAIDZ, Services, AI Jobs, Claude)generate.d/01-index.sh _gen_header() (ligne 17-57) : 17 liens (Dashboard, Alertes, Liens, Montages, Nginx, API, Doc, Modeles, IA Tools, Orchestrator, Secrets, Crons, RAIDZ, Services, Syncthing, LinkedIn, Schema, O2switch)assets/nav.js (ligne 3-23) : 19 liens (Dashboard, Alertes, Liens, Montages, Nginx, API, Doc, Modeles, IA Tools, Orchestrator, AI Jobs, Secrets, Crons, RAIDZ, Services, Syncthing, LinkedIn, Schema, O2switch)templates.sh html_header() (ligne 35-79) : 12 liens (Dashboard, Alertes, Liens, Montages, Nginx, API, Doc, Modeles, Secrets, Crons, RAIDZ, Services) - PAS de IA Tools, Orchestrator, AI Jobs, Claude, Schema, O2switchgenerate.sh generate_header() (ligne 72-116) : 14 liens (Dashboard, Alertes, Liens, Montages, Nginx, API, Doc, Modeles, Secrets, Crons, RAIDZ, Services, Schema, O2switch, Claude)Les listes DIVERGENT clairement. Pages absentes selon la source : claude.html n'est pas dans templates.sh ; architecture.html est dans _common.sh mais nulle part ailleurs ; ai-tools.html, ai-orchestrator.html, ai-jobs.html sont dans nav.js et 01-index.sh mais pas dans _common.sh ni templates.sh.
Verdict : CONFIRME
Verification :
assets/style.css (lignes 8-49) definit les variables : --bg-dark, --bg-card, --bg-card-hover, --bg-input, --border, --text, --text-muted, --text-bright, --green, --yellow, --red, --blue, --purple, --orange, --cyan, --space-*, --radius-*, --shadow-*, --header-height--bg-secondary, --bg-tertiary, --border-color, --text-primary, --text-secondary, --accent-blue, --accent-green, --accent-orange, --accent-red, --bg-hover, --bg-main, --text-dim, --card-bg04-secrets.sh (lignes 25-67), 05-endpoints.sh (lignes 25-88), 18-ai-tools.sh (lignes 26-159), 26-claude.sh (lignes 28-183)Le navigateur ignore les proprietes CSS avec des variables non definies. Le rendu est effectivement degrade.
Verdict : CONFIRME
Verification :
19-ai-orchestrator.sh ligne 13 : local API_URL="http://192.168.1.12:5501" - IP hardcodee, n'utilise PAS get_host_ip18-ai-tools.sh : grep trouve 192.168.1.30 (ligne 226, 243), 192.168.1.10:8006 (ligne 1000), 192.168.1.12:9443 (ligne 1001), 192.168.1.51:9443 (ligne 1002) dans le heredoc HTML20-ai-jobs.sh : utilise get_host_ip pour PROD_IP (ligne 16) mais hardcode le port 5501 (lignes 20, 31), et dans les exemples curl (lignes 1458-1473) utilise 192.168.1.12:5501 en durcollect.sh : 192.168.1.10, 192.168.1.12, 192.168.1.51, etc. hardcodees partout (pas de get_host_ip)Cependant : collect.sh tourne localement, les IPs dans les footers HTML sont des liens d'acces local. La regle CLAUDE.md "JAMAIS d'IP:port dans le code" concerne le code applicatif, pas forcement les scripts de monitoring internes. Neanmoins, l'inconsistance (certains modules utilisent get_host_ip, d'autres pas) est reelle.
Verdict : CONFIRME
Verification :
generate.d/_common.sh ligne 14 : SSH_OPTS="-o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes"collect.d/_common.sh ligne 39 : ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yescollect.sh ligne 52 : ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yesToutes les connexions SSH utilisent StrictHostKeyChecking=no. C'est une mauvaise pratique meme en reseau local controle. StrictHostKeyChecking=accept-new serait preferable.
set -e / gestion d'erreurs silencieuseVerdict : CONFIRME
Verification :
generate.d/ pour set -e : aucun match. Aucun module generate.d ne contient set -e.collect.sh ligne 6 : # Note: set -e retiré car le script gère ses propres erreurs - mais les "propres erreurs" sont 2>/dev/null || echo "" qui masquent silencieusement les echecs.Verdict : CONFIRME
Verification :
26-claude.sh ligne 232, 237, 257, 267, 287, 299 : sed 's/</\</g' | sed 's/>/\>/g' - echappe seulement < et >08-crons.sh lignes 84, 88, 91 : sed 's/</\</g; s/>/\>/g' - meme chose20-ai-jobs.sh ligne 995 : sed 's/</\</g; s/>/\>/g; s/"/\"/g' - echappe aussi " mais PAS & ni '& n'est echappe nulle part, ce qui casse le HTML si la source contient &Verdict : CONFIRME
Verification :
assets/dashboard.css existe (mentionne dans le rapport)templates.sh ligne 18 : <link rel="stylesheet" href="assets/dashboard.css"> - utilise dashboard.cssyolo.html utilise dashboard.css (confirme par grep)style.cssVerdict : CONFIRME
Verification :
25-services.sh lignes 49-240 : Definit son propre CSS complet (fond linear-gradient(135deg, #1a1a2e 0%, #16213e 100%), couleurs hardcodees #10b981, #ef4444, etc.)<head> commence dans un heredoc SERVICESHEAD qui ne contient PAS de <link> vers style.css.nav-links avec seulement 5 liens (Dashboard, API, AI Jobs, Crons, Services)style.css12-raidz.sh a le meme probleme (lignes 49-80 : son propre CSS complet, fond linear-gradient, pas de style.css).
Verdict : CONFIRME
Verification :
shortlinks.html ligne 58 : <nav class="header-nav" id="main-nav"></nav> et ligne 695 : <script src="assets/nav.js"></script>needfinder-v2.html utilise aussi nav.js (confirme par grep)generate.d/*.sh ne genere <nav id="main-nav"> ni n'inclut nav.jsLe rapport est correct : nav.js est quasi-orphelin, pas totalement mort.
Verdict : CONFIRME
Verification :
data-loader.js : seuls matches sont claude.html (qui est une page generee contenant le texte du rapport d'audit) et data-loader.js lui-meme.<script src="assets/data-loader.js">.Verdict : CONFIRME
Verification :
gen_footer_full() definie dans _common.sh ligne 124gen_html_head() definie dans _common.sh ligne 71_common.sh (definitions). Aucun autre fichier ne les appelle.gen_header() et gen_footer() (versions simples), ou leur propre header/footer inline.Verdict : CONFIRME
Verification :
19-ai-orchestrator.sh ligne 650 : setInterval(refreshAll, 15000);visibilityState ou visibilitychange dans ce fichier : aucun matchNote : 20-ai-jobs.sh a le meme probleme avec setInterval a 5000ms (ligne 2153) et 10000ms (ligne 2235), sans visibilitychange.
Verdict : CONFIRME
Verification :
19-ai-orchestrator.sh ligne 13 : API_URL est dans un heredoc quote ('HTMLHEAD'), donc pas d'interpolation dans le head HTML. MAIS les donnees sont injectees via JavaScript fetch (live), pas via heredoc. Ce finding est donc PARTIELLEMENT IMPRECIS pour 19-ai-orchestrator.sh.25-services.sh lignes 280-284 : sed -i "s/CONFIG_SERVICES/$config_services/" - si $config_services contient des caracteres speciaux sed, le sed echoue. Mais les valeurs sont des entiers (jq length), donc risque theorique.26-claude.sh lignes 207-219 : heredoc non-quote (HTMLSTATS) avec $prop_count, $task_count, $backlog_count - entiers, risque theorique.12-raidz.sh heredoc non-quote avec $STOCK8_STATE, $STOCK36_STATE etc. - valeurs provenant de jq, potentiellement non echappees.Finding reel mais risque essentiellement theorique car les sources sont des APIs internes.
Verdict : CONFIRME, severite REDUITE
Verification :
collect.sh ligne 480 : redis-cli -a "urefsLoXibTZ36mtuygcLyNuqzOmIVkF" (Redis PROD)collect.sh ligne 496 : redis-cli -a 'rYfXNQRrVef2LXp8E1B06o4jiT5f5bxs' (Redis DEV).gitignore)Bug REEL. Meme logique que C-01 : les credentials sont dans le git history pousse vers O2switch, mais le repo O2switch est aussi protege par IP. La severite CRITIQUE (meme niveau que C-01) est disproportionnee si le site n'est pas public. Severite reelle : HAUTE (P1).
Verdict : CONFIRME, severite REDUITE
Verification :
collect-gitlab.sh ligne 15 : GITLAB_TOKEN="glpat-yaowLwWBJhXfzJEC8UBC" - token en clair.gitignore)Bug REEL. Meme logique : severite reelle HAUTE (P1), pas CRITIQUE.
git add -A pousse tout sans filtrage vers O2switchVerdict : CONFIRME
Verification :
generate.sh ligne 4910 : git add -A 2>/dev/nullgenerate.sh ligne 4912 : git push o2switch main 2>/dev/null &.gitignore contient : current.json, history/*.json, logs/, archives/, *.tmp, *.bak.gitignore N'EXCLUT PAS : secrets.html, endpoints.html, collect.sh, collect-gitlab.sh, services-registry.jsonTout fichier non dans .gitignore est pousse vers O2switch, y compris les fichiers sensibles.
Verdict : IMPOSSIBLE A VERIFIER EN LECTURE SEULE
Le rapport dit que services-registry.json est owned par root avec permissions 600. Je n'ai pas acces a ls -la pour verifier (bash non disponible). Cependant :
generate.sh ligne 4909 : chown -R gouroubleu:gouroubleu *.json data/ 2>/dev/null || true - tente de corriger les permissions, ce qui implique que le probleme existe25-services.sh ligne 24 : [[ ! -f "$REGISTRY_FILE" ]] && echo '{"services":{}}' > "$REGISTRY_FILE" - fallback si le fichier n'est pas lisible|| true masque l'echec du chownLe finding est PLAUSIBLE base sur le code, mais je ne peux pas confirmer les permissions exactes sans acces systeme.
Verdict : CONFIRME
Verification :
.git/config ligne 10 : url = http://oauth2:glpat-yaowLwWBJhXfzJEC8UBC@192.168.1.196/gouroubleu/dashboard-33800.git.git/ donc n'est pas pousse vers O2switch (le remote "o2switch" pointe vers o2switch:repositories/dashboard.git sans token)Verdict : CONFIRME, severite correctement reduite
Verification :
collect.sh ligne 517 : apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew... (Supabase PROD anon key)collect.sh ligne 533 : apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew... (Supabase DEV anon key)Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
_common.sh ligne 26 : local result=$(jq -r "$query // empty" "$CONFIG_FILE" 2>/dev/null)$query est passe directement a jq sans validationget_host_ip "nginx", get_config ".hosts.pve.ip", etc.$queryRisque purement theorique.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
25-services.sh lignes 280-284 : sed -i "s/CONFIG_SERVICES/$config_services/" etc.$config_services contenait des caracteres speciaux sed (/, &, \), le sed casserait$config_services est le resultat de jq '.services | length' qui retourne un entierRisque purement theorique.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
_common.sh lignes 190-195 : inject_json() fait cat "$JSON_FILE" dans un bloc <script></script>, cela casserait le HTMLinject_json() n'est jamais appelee (grep confirme : definie dans _common.sh, jamais appelee ailleurs)C'est du code mort donc le risque est nul en pratique. Doublement theorique.
Verdict : CONFIRME
Verification :
08-crons.sh ligne 44 : ssh -o ConnectTimeout=5 gouroubleu@... (5 secondes)08-crons.sh ligne 72 : ssh -o ConnectTimeout=5 gouroubleu@... (5 secondes)08-crons.sh ligne 88 : ssh -o ConnectTimeout=5 gouroubleu@... (5 secondes)collect.sh ssh_cmd() : timeout $SSH_TIMEOUT ssh -o ConnectTimeout=5 avec SSH_TIMEOUT=10generate.d/_common.sh : SSH_TIMEOUT=10Les appels SSH dans 08-crons.sh utilisent ConnectTimeout=5 mais PAS timeout (pas de timeout global sur la commande). Les appels dans collect.sh utilisent timeout 10. L'inconsistance est reelle mais mineure.
Note : l'absence de StrictHostKeyChecking=no dans 08-crons.sh (vs les autres scripts qui l'ont) est aussi une inconsistance - les connexions SSH de 08-crons.sh peuvent echouer si les host keys ne sont pas en known_hosts.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
12-raidz.sh ligne 289 : $([ $STOCK8_PERCENT -lt 50 ] && echo 'low' || ...)$STOCK8_PERCENT vaut "null" ou est vide, la comparaison [ -lt 50 ] echoue$STOCK8_PERCENT vient de jq -r '.servers.pve.zfs.stock_8to.percent // 0' qui retourne 0 comme default// 0 dans jq protege, mais si le chemin JSON est invalide, jq retourne 0 (string), et -lt fonctionne sur "0" car bash le traite comme 0Risque theorique tres faible grace au default jq.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
12-raidz.sh ligne 267 : $(echo $STOCK8_STATE | tr '[:upper:]' '[:lower:]')$STOCK8_STATE non quote dans le echo. Si la valeur contenait des espaces ou caracteres glob, le resultat serait incorrectONLINE, DEGRADED, FAULTED, UNAVAILRisque nul en pratique.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
20-ai-jobs.sh utilise innerHTML abondamment (lignes 1512, 1561, 1720, 1904, 1984, 2005, 2039, 2056, 2087, 2162)fetch() vers l'API ai-orchestrator (interne, authentifiee)Risque theorique. Non exploitable en pratique sauf compromission de l'API interne.
Verdict : CONFIRME
Verification :
generate.sh ligne 28 : mkdir -p "$OUTPUT_DIR" ou OUTPUT_DIR="/tmp/dashboard-33800"generate.sh ligne 4895 : cp /tmp/dashboard-33800/*.html /stock_8to/33800-stack/monitoring/ - copie les fichiers mais ne supprime pas les originauxImpact mineur : /tmp est nettoye au reboot, et le serveur a un seul utilisateur.
Verdict : CONFIRME
Verification :
config.json lignes 3-5 : "pve": { "ip": "192.168.1.10", "ip_main": "192.168.1.4" }get_host_ip("pve") retourne 192.168.1.10 (l'IP vmbr0 de la VM PVE, pas l'IP de management 192.168.1.4)ip_main existe mais n'est utilise par aucune fonction de _common.shL'ambiguite est reelle : 192.168.1.4 est l'IP de l'hote physique Proxmox, 192.168.1.10 est probablement une autre interface. Les scripts utilisent .10 pour SSH vers PVE, ce qui peut etre correct ou pas selon le contexte.
Verdict : CONFIRME
Verification :
config.json ligne 161 : "browser-connector": { "port": 5401 }La contradiction est reelle entre config.json et la documentation. CLAUDE.md est la source de verite basee sur l'experience reelle. config.json est probablement faux.
Verdict : CONFIRME
Verification :
config.json ligne 154 : "connectors-api": { "port": 5400 }Meme pattern que U-12. Le port dans config.json (5400) est probablement le port interne du container, tandis que 5403 est le port expose derriere le sidecar WireGuard. Les deux sont "corrects" dans des contextes differents, mais la confusion est reelle.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
generate.sh ligne 4910 : git add -A 2>/dev/nullgenerate.sh ligne 4911 : git commit -m "Update: ..."generate.sh ligne 4912 : git push o2switch main 2>/dev/null &Le push est lance en & (background). Si une autre execution de generate.sh demarre avant que le push ne finisse, le prochain git add -A et git commit pourraient entrer en conflit avec le push en cours.
En pratique, generate.sh est lance par cron (1x/heure) et le push est rapide, donc le risque est tres faible.
Verdict : CONFIRME
Verification :
collect.sh ligne 32 : BUFFER_DIR="/tmp/monitoring-buffer"collect.sh ligne 46 : mkdir -p "$BUFFER_DIR"collect.sh ligne 20 : trap "rm -f $LOCKFILE" EXIT - le trap ne nettoie que le lockfile/tmp/monitoring-buffer/ n'est jamais supprimeLes fichiers .json et .log s'accumulent dans /tmp/monitoring-buffer/. Impact mineur.
Verdict : CONFIRME
Verification :
generate.sh lignes 4926-4927 :NOTIF_BODY="${PAGES_COUNT} pages - ${TIMESTAMP}
https://dashboard.nowhere84.com"
-d "{\"title\": \"${NOTIF_TITLE}\", \"body\": \"${NOTIF_BODY}\", \"level\": \"info\"}"NOTIF_BODY contient un retour a la ligne reel (pas \n), ce qui casse le JSON car le JSON n'accepte pas les newlines litterales dans les stringsBug REEL et affecte chaque execution de generate.sh.
Verdict : CONFIRME - RISQUE THEORIQUE
Verification :
collect.sh lignes 10-18 : Le lockfile contient le PID, verifie via kill -0flock serait plus robusteVerdict : CONFIRME - RISQUE THEORIQUE
Verification :
26-claude.sh ligne 230 : for file in $propositions; do$propositions est le resultat de ls -t "$CLAUDE_DIR/propositions/"*.md (ligne 15)$propositions, bash fait du word splitting et du globbing*, ?), la boucle casseraitJJ-MM-AAAA-HH-MM-description.md ne contient pas d'espacesRisque theorique protege par la convention de nommage.
| ID | Finding | Verdict | Commentaire |
|---|---|---|---|
| C-01 | Credentials en clair dans HTML | CONFIRME - severite P1 pas P0 | Dashboard protege par IP, pas public |
| C-02 | 4+ navigations desynchronisees | CONFIRME | Au moins 6 systemes distincts identifies |
| C-03 | Variables CSS non definies | CONFIRME | 14+ variables absentes de style.css |
| C-04 | IPs hardcodees | CONFIRME | Inconsistance get_host_ip vs hardcode |
| C-05 | StrictHostKeyChecking=no | CONFIRME | Toutes les connexions SSH |
| C-06 | Absence de set -e | CONFIRME | Aucun module generate.d n'a set -e |
| C-07 | Echappement HTML incomplet | CONFIRME | & non echappe nulle part |
| C-08 | dashboard.css obsolete | CONFIRME | Utilise par 2 fichiers seulement |
| C-09 | 25-services.sh autonome | CONFIRME | CSS/nav completement independants |
| C-10 | nav.js quasi-mort | CONFIRME | 2 pages statiques seulement |
| C-11 | data-loader.js jamais utilise | CONFIRME | Code mort |
| C-12 | gen_footer_full/gen_html_head inutilisees | CONFIRME | Jamais appelees |
| C-13 | Polling sans visibilitychange | CONFIRME | 15s interval sans pause |
| C-14 | Variables non quotees heredocs | CONFIRME | Risque theorique (valeurs = entiers) |
| C-15 | Redis passwords hardcodes | CONFIRME - severite P1 pas P0 | Dashboard protege par IP |
| C-16 | Token GitLab hardcode | CONFIRME - severite P1 pas P0 | Dashboard protege par IP |
| C-17 | git add -A sans filtrage | CONFIRME | .gitignore incomplet |
| C-18 | services-registry.json root:600 | NON VERIFIABLE | Plausible base sur le code (chown |
| U-01 | Token dans .git/config URL | CONFIRME | Token en clair dans remote origin |
| U-02 | JWT Supabase anon keys | CONFIRME - severite faible | Cles anon = publiques par design |
| U-03 | Injection jq get_config | CONFIRME - theorique | Appels fixes uniquement |
| U-04 | sed -i regex content | CONFIRME - theorique | Valeurs = entiers |
| U-05 | inject_json sans escape | CONFIRME - theorique | Fonction jamais appelee (code mort) |
| U-06 | SSH timeout inconsistant | CONFIRME | ConnectTimeout sans timeout global |
| U-07 | Arithmetic sans validation | CONFIRME - theorique | Default jq protege |
| U-08 | Variable non quotee echo/tr | CONFIRME - theorique | Etats ZFS = mots simples |
| U-09 | XSS innerHTML | CONFIRME - theorique | APIs internes controlees |
| U-10 | /tmp non nettoye | CONFIRME | Impact mineur |
| U-11 | IP PVE ip vs ip_main | CONFIRME | Ambiguite .4 vs .10 |
| U-12 | Port browser-connector 5401/5404 | CONFIRME | config.json probablement faux |
| U-13 | Port connectors-api 5400/5403 | CONFIRME | Port interne vs expose |
| U-14 | Race condition git push & | CONFIRME - theorique | Cron 1x/h, push rapide |
| U-15 | /tmp/monitoring-buffer non nettoye | CONFIRME | Trap ne nettoie que lockfile |
| U-16 | Notification JSON cassee newline | CONFIRME | Bug reel affectant chaque execution |
| U-17 | Lockfile PID reuse | CONFIRME - theorique | flock serait mieux |
| U-18 | Globbing non protege for loop | CONFIRME - theorique | Convention nommage protege |