#!/bin/sh # ===================================================================================================== # gentle-audit.sh — audit de sécurité en LECTURE SEULE pour macOS et Linux (POSIX sh) # Source : https://gentledesigns.studio/securite-numerique/scripts (jumeau Windows : gentle-audit.ps1) # © 2026 gentledesigns.studio — Tous droits réservés. Lecture et exécution libres ; réutilisation sur demande. # # Ce que fait ce script, en clair : # - il LIT des réglages du système (mises à jour, pare-feu, chiffrement, comptes...) ; # - il ne modifie RIEN : aucun réglage, aucun service, aucun fichier système ; # - il n'utilise PAS le réseau : rien n'est téléchargé, rien n'est envoyé # (donc ni « softwareupdate --list », ni « apt update », ni « dnf check-update », ni fwupd) ; # - il n'a pas besoin de sudo et ne l'appelle jamais (sans droits, certains points restent « inconnus ») ; # - sa seule écriture : un rapport JSON dans le dossier courant, # gentle-audit--AAAAMMJJ-HHMMSS.json, que tu peux lire avant d'en faire quoi que ce soit. # Aucun secret dans le rapport : ni mot de passe, ni nom de réseau Wi-Fi, ni nom d'utilisateur. # # Lancement conseillé (télécharger -> lire -> lancer), depuis le dossier où tu veux le rapport : # sh ./gentle-audit.sh # # Codes de sortie (identiques dans gentle-audit.ps1) : # 0 = rapport écrit # 1 = erreur inattendue (rien n'a été modifié) # 2 = système non pris en charge (ce script est pour macOS et Linux ; Windows : gentle-audit.ps1) # 3 = rapport impossible à écrire (dossier courant en lecture seule, ou fichier déjà présent) # ===================================================================================================== set -eu # Toute sortie imprévue (commande qui échoue hors d'un check isolé) devient le code 1. ERR_MSG="Erreur inattendue, rien n'a été modifié : code" trap '_rc=$?; if [ "$_rc" -ne 0 ] && [ "$_rc" -ne 2 ] && [ "$_rc" -ne 3 ]; then echo "$ERR_MSG $_rc"; exit 1; fi' EXIT # Sorties des commandes en anglais, quelle que soit la langue du système (analyse indépendante de la langue). LC_ALL=C export LC_ALL NO_RIGHTS="non vérifié sans droits administrateur" JSON_CHECKS="" SUMMARY="" N_OK=0 N_KO=0 N_UNK=0 N_ALL=0 NL=' ' have() { command -v "$1" >/dev/null 2>&1; } json_esc() { printf '%s' "$1" | tr '\011\012\015' ' ' | tr -d '\000-\010\013\014\016-\037' | sed 's/\\/\\\\/g; s/"/\\"/g' } # record ID RESULTAT TITRE DETAIL record() { _id=$1; _res=$2; _title=$3; _detail=$4 _entry=" { \"id\": \"$(json_esc "$_id")\", \"result\": \"$_res\", \"detail\": \"$(json_esc "$_detail")\" }" if [ -z "$JSON_CHECKS" ]; then JSON_CHECKS=$_entry; else JSON_CHECKS="$JSON_CHECKS,$NL$_entry"; fi case "$_res" in ok) _label="[ok] "; N_OK=$((N_OK + 1)) ;; ko) _label="[ko] "; N_KO=$((N_KO + 1)) ;; *) _label="[inconnu]"; N_UNK=$((N_UNK + 1)) ;; esac N_ALL=$((N_ALL + 1)) SUMMARY="$SUMMARY$(printf '%s %-9s %s' "$_label" "$_id" "$_title")$NL $_detail$NL" } # Chaque check est isolé : appelé dans un « || », une commande absente ou refusée ne stoppe pas le script. # Un check pose RES et DETAIL ; s'il échoue sans conclure, on note « unknown ». run_check() { _cid=$1; _ctitle=$2; _cfn=$3 RES=unknown DETAIL="lecture impossible (commande indisponible ou refusée)" "$_cfn" || true record "$_cid" "$RES" "$_ctitle" "$DETAIL" } set_r() { RES=$1; DETAIL=$2; } # ------------------------------------------------------------------------------------------- 0. Système case "$(uname -s 2>/dev/null || echo inconnu)" in Darwin) OS=macos ;; Linux) OS=linux ;; *) echo "Ce script est pour macOS et Linux. Sur Windows, utilise gentle-audit.ps1." exit 2 ;; esac echo "" echo "gentle-audit : audit de sécurité en LECTURE SEULE (gentledesigns.studio)" echo "Ce script ne modifie rien : aucun réglage, aucun service, aucun fichier système." echo "Il n'utilise pas le réseau : rien n'est téléchargé, rien n'est envoyé." echo "Il n'a pas besoin des droits administrateur : sans eux, certains points restent « inconnus »." echo "Seule écriture : un rapport JSON dans le dossier courant." echo "Ce rapport s'importe sur la page Scripts du site : il est lu dans ton navigateur, jamais envoyé." echo "Codes de sortie : 0 rapport écrit, 1 erreur inattendue, 2 système non pris en charge, 3 rapport impossible à écrire." echo "" IS_ROOT=0 [ "$(id -u 2>/dev/null || echo 1)" = "0" ] && IS_ROOT=1 # (TODAY retiré le 16/10/2026 : plus aucun verdict ne dépend d'une date codée en dur — cf. lnx_supported) # =========================================================================================== macOS # Commandes retenues : marquées ✅ sans [NON VÉRIFIÉ] dans le corpus, sorties non mesurées sur Mac (porte G7). # Exclu : com.apple.SoftwareUpdate (payload déprécié en macOS 26, retiré en macOS 27 selon Apple), # sysadminctl -screenLock, nvram (Localiser), xprotect, bputil, LDMGlobalEnabled (non documentés). mac_gatekeeper() { _o=$(spctl --status 2>&1) || true case "$_o" in *"assessments enabled"*) set_r ok "Gatekeeper actif : seules les apps de l'App Store ou de développeurs identifiés se lancent sans alerte" ;; *"assessments disabled"*) set_r ko "Gatekeeper désactivé : n'importe quelle app peut se lancer" ;; *) return 1 ;; esac } mac_timemachine() { _o=$(tmutil destinationinfo 2>&1) || true if printf '%s\n' "$_o" | grep -q '^ID'; then set_r ok "au moins une destination Time Machine configurée" elif printf '%s' "$_o" | grep -qi 'no destinations'; then set_r ko "aucune destination Time Machine : branche un disque externe et active Time Machine" else return 1; fi } mac_autologin() { if defaults read /Library/Preferences/com.apple.loginwindow autoLoginUser >/dev/null 2>&1; then set_r ko "ouverture de session automatique activée : un Mac volé s'ouvre sur ta session" else set_r ok "pas d'ouverture de session automatique" fi } mac_filevault() { _o=$(fdesetup status 2>&1) || true case "$_o" in *"FileVault is On"*) set_r ok "FileVault actif : disque chiffré" ;; *"FileVault is Off"*) set_r ko "FileVault désactivé : données lisibles si le Mac est volé" ;; *) return 1 ;; esac } mac_sip() { _o=$(csrutil status 2>&1) || true case "$_o" in *"status: enabled"*) set_r ok "protection de l'intégrité du système active" ;; *"status: disabled"*) set_r ko "protection de l'intégrité du système désactivée" ;; *) set_r unknown "état de la protection de l'intégrité du système non standard" ;; esac } SFW=/usr/libexec/ApplicationFirewall/socketfilterfw mac_firewall() { [ -x "$SFW" ] || return 1 _o=$("$SFW" --getglobalstate 2>&1) || true case "$_o" in *"State = 1"*|*"State = 2"*) set_r ok "coupe-feu actif" ;; *"State = 0"*) set_r ko "coupe-feu désactivé (Réglages Système > Réseau > Coupe-feu)" ;; *disabled*) set_r ko "coupe-feu désactivé (Réglages Système > Réseau > Coupe-feu)" ;; *enabled*) set_r ok "coupe-feu actif" ;; *) return 1 ;; esac } mac_stealth() { [ -x "$SFW" ] || return 1 _o=$("$SFW" --getstealthmode 2>&1) || true case "$_o" in *disabled*|*" off"*) set_r ko "mode furtif désactivé" ;; *enabled*|*" on"*) set_r ok "mode furtif actif" ;; *) return 1 ;; esac } # L2-MAC-4 est un constat (gravité 1) : un compte admin est la configuration par défaut d'un Mac perso, # ce n'est donc pas un « ko ». Le ko est réservé à L5-MAC-3, qui demande l'action (compte standard + admin # séparé). Deux fonctions distinctes pour ne pas afficher un faux rouge au niveau 2. mac_admin_info() { _g=$(id -Gn 2>/dev/null) || return 1 case " $_g " in *" admin "*) set_r ok "ton compte est administrateur (normal sur un Mac perso ; option avancée au niveau 5)" ;; *) set_r ok "compte standard" ;; esac } mac_admin() { _g=$(id -Gn 2>/dev/null) || return 1 case " $_g " in *" admin "*) set_r ko "ton compte est administrateur (option avancée : un compte standard au quotidien, l'admin à part)" ;; *) set_r ok "tu utilises un compte standard" ;; esac } mac_supported() { _v=$(sw_vers -productVersion 2>/dev/null) || return 1 _arch=$(uname -m 2>/dev/null || echo "?") _major=${_v%%.*} case "$_major" in ''|*[!0-9]*) return 1 ;; esac if [ "$_major" -ge 27 ]; then set_r ok "macOS $_v : version actuelle" elif [ "$_major" -ge 26 ]; then if [ "$_arch" = "x86_64" ]; then set_r ok "macOS $_v : dernière version pour Mac Intel, fin des correctifs à anticiper" else set_r ok "macOS $_v : encore corrigé, macOS 27 est disponible"; fi elif [ "$_major" -eq 15 ]; then set_r unknown "macOS $_v : Apple ne publie pas de calendrier de fin de support, vérifie que Mise à jour de logiciels propose encore des correctifs" else set_r ko "macOS $_v : version ancienne, très probablement plus de correctifs de sécurité"; fi } mac_mdm() { _o=$(profiles status -type enrollment 2>&1) || true case "$_o" in *"MDM enrollment: Yes"*) set_r unknown "Mac géré par une organisation (MDM) : normal sur un Mac professionnel, suspect sur un Mac personnel" ;; *"MDM enrollment: No"*) set_r ok "aucune gestion à distance (MDM) inscrite" ;; *) return 1 ;; esac } # =========================================================================================== Linux OS_ID=""; OS_VID=""; OS_PRETTY="" if [ "$OS" = linux ] && [ -r /etc/os-release ]; then # Lu, jamais exécuté (pas de « . /etc/os-release »). OS_ID=$(sed -n 's/^ID=//p' /etc/os-release | tr -d '"' | head -n 1) OS_VID=$(sed -n 's/^VERSION_ID=//p' /etc/os-release | tr -d '"' | head -n 1) OS_PRETTY=$(sed -n 's/^PRETTY_NAME=//p' /etc/os-release | tr -d '"' | head -n 1) fi lnx_autoupdate() { _on=""; _off="" _f=/etc/apt/apt.conf.d/20auto-upgrades if [ -r "$_f" ]; then _v=$(sed -n 's/^[[:space:]]*APT::Periodic::Unattended-Upgrade[[:space:]]*"\([0-9]*\)".*/\1/p' "$_f" | tail -n 1) case "$_v" in ''|0) _off="$_off unattended-upgrades" ;; *) _on="$_on unattended-upgrades" ;; esac fi if have systemctl; then for _t in dnf-automatic.timer dnf5-automatic.timer; do _s=$(systemctl is-enabled "$_t" 2>/dev/null) || true [ "$_s" = enabled ] && _on="$_on $_t" done fi # GNOME Logiciels est compté À PART : `download-updates` (vrai par défaut) ne fait que TÉLÉCHARGER # les mises à jour, il ne les installe pas automatiquement sur la plupart des distributions. # Le compter comme un mécanisme actif renvoyait un faux « ok » alors qu'unattended-upgrades était coupé. _gnome="" if have gsettings; then _g=$(gsettings get org.gnome.software download-updates 2>/dev/null) || _g="" case "$_g" in true) _gnome="telecharge" ;; false) _off="$_off GNOME-Logiciels" ;; esac fi _pend="" if have apt; then _n=$(apt list --upgradable 2>/dev/null | awk '/upgradable from/ {n++} END {print n+0}') || _n="" [ -n "$_n" ] && _pend=" ; cache local : $_n mise(s) à jour en attente (selon la dernière actualisation)" fi if [ -n "$_on" ]; then set_r ok "mises à jour automatiques actives :$_on$_pend" elif [ -n "$_off" ]; then set_r ko "mises à jour automatiques désactivées :$_off$_pend" elif [ -n "$_gnome" ]; then set_r unknown "GNOME Logiciels télécharge les mises à jour mais ne les installe pas tout seul sur la plupart des distributions : active unattended-upgrades (Debian/Ubuntu) ou dnf-automatic (Fedora) pour qu'elles s'appliquent$_pend" else set_r unknown "aucun mécanisme reconnu (unattended-upgrades, dnf-automatic, GNOME Logiciels)$_pend"; fi } lnx_supported() { # AUCUN CALCUL DE DATE ICI, volontairement (audit G2 §3, 16/10/2026). # Les dates de fin de support precedemment codees en dur (Debian 12 -> 2026-07-12, # Ubuntu 22.04 -> 2027-05-31, LTS Debian -> 2028-06-30) n'ont pas pu etre relues sur les pages # officielles. Plutot que de rendre un verdict date non source — qui se perime en silence et fait # autorite a tort — le script ne compare que les NUMEROS de version (faits stables et verifiables # hors ligne) et renvoie l'utilisateur a la page de cycle de vie de sa distribution pour la date. # Pour reintroduire un calcul de date : relire d'abord ubuntu.com/about/release-cycle et # wiki.debian.org/LTS, puis citer la date de lecture a cote de chaque valeur. [ -n "$OS_ID" ] || return 1 _num=$(printf '%s' "$OS_VID" | awk -F. '{ if ($1 ~ /^[0-9]+$/) printf "%d", $1 * 100 + $2 }') case "$OS_ID" in ubuntu) [ -n "$_num" ] || return 1 # Les LTS sont les versions en annee paire, mois 04. case "$OS_VID" in 26.04) set_r ok "$OS_PRETTY : LTS la plus recente connue du script" ;; 22.04|24.04) set_r unknown "$OS_PRETTY : LTS, mais son support standard se termine — verifie la date sur ubuntu.com/about/release-cycle et prevois 26.04 LTS" ;; *) if [ "$_num" -gt 2604 ]; then set_r ok "$OS_PRETTY : version plus recente que la table du script, verifie ubuntu.com/about/release-cycle" else set_r ko "$OS_PRETTY : version intermediaire ou ancienne, plus de correctifs (passe a 26.04 LTS)"; fi ;; esac ;; debian) [ -n "$_num" ] || { set_r unknown "Debian testing/sid : pas de numero de version, suivi propre"; return 0; } _maj=$((_num / 100)) if [ "$_maj" -ge 13 ]; then set_r ok "$OS_PRETTY : version stable actuelle" elif [ "$_maj" -eq 12 ]; then set_r unknown "$OS_PRETTY : plus la stable actuelle — selon la date, encore en support de securite ou deja passee en LTS (support allege). Verifie wiki.debian.org/LTS et prevois Debian 13" elif [ "$_maj" -eq 11 ]; then set_r unknown "$OS_PRETTY : au mieux en LTS, verifie wiki.debian.org/LTS et prevois Debian 13" else set_r ko "$OS_PRETTY : plus de correctifs de securite, passe a Debian 13"; fi ;; fedora) [ -n "$_num" ] || return 1 _maj=$((_num / 100)) if [ "$_maj" -ge 44 ]; then set_r ok "$OS_PRETTY : version maintenue" elif [ "$_maj" -eq 43 ]; then set_r ok "$OS_PRETTY : maintenue jusqu'à environ 4 semaines après la sortie de Fedora 45" else set_r ko "$OS_PRETTY : plus de correctifs, passe à la dernière Fedora"; fi ;; *) set_r unknown "${OS_PRETTY:-$OS_ID} : distribution absente de la table du script, compare à sa page de cycle de vie" ;; esac } lnx_screenlock() { have gsettings || { set_r unknown "pas de GNOME détecté : vérifie le verrouillage automatique dans les réglages de ton bureau"; return 0; } _le=$(gsettings get org.gnome.desktop.screensaver lock-enabled 2>/dev/null) || { set_r unknown "réglages GNOME absents : vérifie le verrouillage automatique dans les réglages de ton bureau"; return 0; } _idle=$(gsettings get org.gnome.desktop.session idle-delay 2>/dev/null | awk '{print $NF}') || _idle="" case "$_idle" in ''|*[!0-9]*) _idle="" ;; esac if [ "$_le" != true ]; then set_r ko "verrouillage de l'écran désactivé" elif [ -z "$_idle" ]; then set_r unknown "verrouillage actif, délai de mise en veille illisible" elif [ "$_idle" -eq 0 ]; then set_r ko "l'écran ne s'éteint jamais seul : il ne se verrouille donc pas" elif [ "$_idle" -gt 300 ]; then set_r ko "verrouillage après $((_idle / 60)) min : vise 5 min au plus" else set_r ok "verrouillage automatique après $((_idle / 60)) min d'inactivité"; fi } lnx_repos() { _third=""; _seen=0 if [ -d /etc/apt ]; then _seen=1 for _f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do [ -r "$_f" ] || continue _hosts=$(awk ' /^[[:space:]]*deb(-src)?[[:space:]]/ { for (i = 2; i <= NF; i++) if ($i ~ /:\/\//) { print $i; break } } /^[[:space:]]*URIs:/ { for (i = 2; i <= NF; i++) print $i } ' "$_f" | sed 's#^[a-z+]*://##; s#/.*##; s#.*@##') for _h in $_hosts; do case "$_h" in deb.debian.org|security.debian.org|*.debian.org|archive.ubuntu.com|*.archive.ubuntu.com|security.ubuntu.com|ports.ubuntu.com|esm.ubuntu.com) ;; *) _third="$_third $_h" ;; esac done done fi if [ -d /etc/yum.repos.d ]; then _seen=1 _ids=$(cat /etc/yum.repos.d/*.repo 2>/dev/null | awk ' /^\[.*\]/ { if (id != "" && en) print id; id = substr($0, 2, length($0) - 2); en = 1; next } /^[[:space:]]*enabled[[:space:]]*=/ { v = $0; sub(/.*=[[:space:]]*/, "", v); en = (v == "1" || v == "true" || v == "yes") } END { if (id != "" && en) print id }') for _i in $_ids; do case "$_i" in fedora|fedora-*|updates|updates-*|rawhide|rawhide-*) ;; *) _third="$_third $_i" ;; esac done fi if have flatpak; then _seen=1 _remotes=$(flatpak remotes --columns=url 2>/dev/null | sed 's#^[a-z+]*://##; s#/.*##') || _remotes="" for _h in $_remotes; do case "$_h" in dl.flathub.org|flathub.org) ;; *) _third="$_third $_h" ;; esac done fi [ "$_seen" -eq 1 ] || return 1 _third=$(printf '%s\n' $_third | sort -u | head -n 8 | tr '\n' ' ' | sed 's/ *$//; s/ /, /g') if [ -z "$_third" ]; then set_r ok "uniquement des dépôts officiels de la distribution (et Flathub)" else set_r unknown "dépôts tiers à passer en revue : $_third — chacun doit venir d'un éditeur de confiance"; fi } lnx_emptypw() { [ "$IS_ROOT" -eq 1 ] || { set_r unknown "$NO_RIGHTS"; return 0; } [ -r /etc/shadow ] || return 1 _n=$(awk -F: '($2 == "") { n++ } END { print n + 0 }' /etc/shadow) if [ "$_n" -eq 0 ]; then set_r ok "aucun compte sans mot de passe" else set_r ko "$_n compte(s) sans mot de passe"; fi } # SSH : calculé une fois, rapporté sous L1-LNX-3 (mots de passe) et L5-LNX-2 (durcissement). SSH1_RES=unknown; SSH1_DET="état du serveur SSH illisible" SSH5_RES=unknown; SSH5_DET="état du serveur SSH illisible" lnx_ssh_eval() { if [ ! -x /usr/sbin/sshd ] && ! have sshd; then SSH1_RES=ok; SSH1_DET="aucun serveur SSH installé"; SSH5_RES=ok; SSH5_DET="aucun serveur SSH installé"; return 0 fi have systemctl || { SSH1_DET="serveur SSH installé, état du service illisible (systemctl absent)"; SSH5_DET=$SSH1_DET; return 0; } _active=0 for _u in ssh.service sshd.service ssh.socket sshd.socket; do _s=$(systemctl is-active "$_u" 2>/dev/null) || true [ "$_s" = active ] && _active=1 done if [ "$_active" -eq 0 ]; then SSH1_RES=ok; SSH1_DET="serveur SSH installé mais arrêté"; SSH5_RES=ok; SSH5_DET="serveur SSH installé mais arrêté (désinstalle-le si inutile)"; return 0 fi if [ "$IS_ROOT" -ne 1 ]; then SSH1_DET="serveur SSH actif, configuration $NO_RIGHTS"; SSH5_DET=$SSH1_DET; return 0; fi _cfg=$(sshd -T 2>/dev/null) || { SSH1_DET="serveur SSH actif, configuration illisible"; SSH5_DET=$SSH1_DET; return 0; } _pa=$(printf '%s\n' "$_cfg" | awk '$1 == "passwordauthentication" { print $2 }') _prl=$(printf '%s\n' "$_cfg" | awk '$1 == "permitrootlogin" { print $2 }') if [ "$_pa" = no ]; then SSH1_RES=ok; SSH1_DET="SSH actif, connexion par clé uniquement" else SSH1_RES=ko; SSH1_DET="SSH actif et accepte les mots de passe : préfère les clés"; fi case "$_prl" in no|prohibit-password|without-password) if [ "$_pa" = no ]; then SSH5_RES=ok; SSH5_DET="SSH durci : pas de mot de passe, pas de connexion root par mot de passe" else SSH5_RES=ko; SSH5_DET="SSH accepte encore les mots de passe"; fi ;; *) SSH5_RES=ko; SSH5_DET="SSH autorise la connexion root ($_prl)" ;; esac } lnx_ssh1() { set_r "$SSH1_RES" "$SSH1_DET"; } lnx_ssh5() { set_r "$SSH5_RES" "$SSH5_DET"; } lnx_luks() { have findmnt && have lsblk || return 1 _src=$(findmnt -n -o SOURCE / 2>/dev/null | sed 's/\[.*//') || return 1 case "$_src" in /dev/*) ;; *) set_r unknown "racine sur un système de fichiers virtuel ($_src) : chiffrement non déterminable (conteneur ?)"; return 0 ;; esac _types=$(lsblk -s -n -o TYPE "$_src" 2>/dev/null) || return 1 if printf '%s\n' "$_types" | grep -qx 'crypt'; then set_r ok "disque système chiffré (LUKS)" else set_r ko "disque système non chiffré : le chiffrement se choisit à l'installation"; fi } lnx_firewall() { _on=""; _off="" if have firewall-cmd; then _s=$(firewall-cmd --state 2>/dev/null) || true case "$_s" in running) _on="firewalld" ;; *) _off="firewalld" ;; esac fi if [ -r /etc/ufw/ufw.conf ]; then if grep -q '^[[:space:]]*ENABLED=yes' /etc/ufw/ufw.conf; then _on="$_on ufw"; else _off="$_off ufw"; fi fi if [ -z "$_on" ] && [ "$IS_ROOT" -eq 1 ] && have nft; then _c=$(nft list ruleset 2>/dev/null | grep -c 'chain ') || _c=0 [ "$_c" -gt 0 ] && _on="nftables ($_c chaîne(s))" fi _on=${_on# } if [ -n "$_on" ]; then set_r ok "pare-feu actif : $_on" elif [ -n "$_off" ]; then set_r ko "pare-feu installé mais inactif :$_off" elif [ "$IS_ROOT" -eq 1 ]; then set_r ko "aucun pare-feu détecté (ni ufw, ni firewalld, ni règles nftables)" else set_r unknown "ni ufw ni firewalld détecté ; règles nftables $NO_RIGHTS"; fi } lnx_listeners() { [ -r /proc/net/tcp ] || return 1 # /proc/net/tcp{,6} : état 0A = en écoute. Adresse locale en hexadécimal ; 127.x et ::1 = la machine seule. _ports=$(cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | awk ' NR > 0 && $4 == "0A" { split($2, a, ":"); ip = a[1]; port = a[2] if (length(ip) == 8 && substr(ip, 7, 2) == "7F") next if (ip == "00000000000000000000000001000000") next if (length(ip) == 32 && substr(ip, 1, 24) == "0000000000000000FFFF0000" && substr(ip, 31, 2) == "7F") next print port }' | sort -u) _list="" for _p in $_ports; do _list="$_list $(printf '%d' "0x$_p")"; done _list=$(printf '%s\n' $_list | sort -n -u | tr '\n' ' ' | sed 's/ *$//; s/ /, /g') if [ -z "$_list" ]; then set_r ok "aucun service TCP n'écoute en dehors de la machine" else set_r unknown "ports TCP ouverts au réseau : $_list — chacun doit correspondre à un service que tu utilises"; fi } lnx_secureboot() { if have mokutil; then _o=$(mokutil --sb-state 2>&1) || true case "$_o" in *"SecureBoot enabled"*) set_r ok "Secure Boot actif"; return 0 ;; *"SecureBoot disabled"*) set_r ko "Secure Boot désactivé (réglage du firmware UEFI)"; return 0 ;; esac fi if [ ! -d /sys/firmware/efi ]; then set_r unknown "démarrage non UEFI (ou conteneur) : Secure Boot sans objet ou illisible" else set_r unknown "démarrage UEFI, état de Secure Boot illisible (mokutil absent)"; fi } lnx_mac() { if [ -r /sys/fs/selinux/enforce ]; then case "$(cat /sys/fs/selinux/enforce 2>/dev/null)" in 1) set_r ok "SELinux actif en mode Enforcing"; return 0 ;; 0) set_r ko "SELinux en mode Permissive : il observe sans bloquer"; return 0 ;; esac fi if [ -r /sys/module/apparmor/parameters/enabled ]; then case "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" in Y) set_r ok "AppArmor actif"; return 0 ;; esac fi set_r ko "ni AppArmor ni SELinux actif" } lnx_wifi() { have nmcli || return 1 _uuids=$(nmcli -t -f UUID,TYPE connection show 2>/dev/null | awk -F: '$2 == "802-11-wireless" { print $1 }') || return 1 _n=0; _weak=0 for _u in $_uuids; do _n=$((_n + 1)) _k=$(nmcli -g 802-11-wireless-security.key-mgmt connection show "$_u" 2>/dev/null) || _k="" case "$_k" in ''|none) _weak=$((_weak + 1)) ;; esac done if [ "$_n" -eq 0 ]; then set_r ok "aucun réseau Wi-Fi mémorisé" elif [ "$_weak" -eq 0 ]; then set_r ok "$_n réseau(x) Wi-Fi mémorisé(s), tous chiffrés (WPA)" else set_r ko "$_weak réseau(x) Wi-Fi mémorisé(s) sur $_n ouvert(s) ou en WEP : oublie ceux qui ne servent plus"; fi } lnx_sysctl() { _bad=""; _miss="" _chk() { _v=$(cat "/proc/sys/$1" 2>/dev/null) || { _miss="$_miss $2"; return 0; } case "$_v" in ''|*[!0-9]*) _miss="$_miss $2"; return 0 ;; esac [ "$_v" -ge "$3" ] || _bad="$_bad $2=$_v" } _chk kernel/kptr_restrict kernel.kptr_restrict 1 _chk kernel/dmesg_restrict kernel.dmesg_restrict 1 _chk kernel/yama/ptrace_scope kernel.yama.ptrace_scope 1 _chk net/ipv4/conf/all/rp_filter net.ipv4.conf.all.rp_filter 1 if [ -n "$_bad" ]; then set_r ko "paramètres noyau permissifs :$_bad" elif [ -n "$_miss" ]; then set_r unknown "paramètres illisibles :$_miss" else set_r ok "kptr_restrict, dmesg_restrict, ptrace_scope et rp_filter durcis"; fi } # ------------------------------------------------------------------------------------------- 1. Checks if [ "$OS" = macos ]; then OS_VERSION="macOS $(sw_vers -productVersion 2>/dev/null || echo '?') ($(uname -m 2>/dev/null || echo '?'))" run_check L0-MAC-3 "Apps de l'App Store ou de développeurs identifiés (Gatekeeper)" mac_gatekeeper run_check L0-MAC-4 "Sauvegarde Time Machine configurée" mac_timemachine run_check L1-MAC-4 "Pas d'ouverture de session automatique" mac_autologin run_check L2-MAC-1 "Disque chiffré (FileVault)" mac_filevault run_check L2-MAC-2 "Protection de l'intégrité du système (SIP)" mac_sip run_check L2-MAC-3 "Coupe-feu actif" mac_firewall run_check L2-MAC-4 "Type de ton compte du quotidien" mac_admin_info run_check L2-MAC-5 "Version de macOS encore corrigée" mac_supported run_check L2-MAC-6 "Pas de gestion à distance (MDM) inconnue" mac_mdm run_check L5-MAC-3 "Compte standard et compte admin séparé" mac_admin run_check L5-MAC-4 "Mode furtif du coupe-feu" mac_stealth else OS_VERSION=${OS_PRETTY:-"Linux $(uname -r 2>/dev/null || echo '?')"} lnx_ssh_eval || true run_check L0-LNX-1 "Mises à jour de sécurité automatiques" lnx_autoupdate run_check L0-LNX-2 "Distribution encore supportée" lnx_supported run_check L0-LNX-3 "Verrouillage automatique de l'écran" lnx_screenlock run_check L0-LNX-4 "Dépôts officiels uniquement" lnx_repos run_check L1-LNX-2 "Aucun compte sans mot de passe" lnx_emptypw run_check L1-LNX-3 "SSH : clés plutôt que mots de passe" lnx_ssh1 run_check L2-LNX-1 "Disque système chiffré (LUKS)" lnx_luks run_check L2-LNX-2 "Pare-feu actif (ufw ou firewalld)" lnx_firewall run_check L2-LNX-3 "Services qui écoutent sur le réseau" lnx_listeners run_check L2-LNX-4 "Secure Boot activé" lnx_secureboot run_check L2-LNX-5 "AppArmor ou SELinux actif" lnx_mac run_check L3-LNX-3 "Wi-Fi mémorisés chiffrés" lnx_wifi run_check L5-LNX-2 "SSH durci (pas de root, clés uniquement)" lnx_ssh5 run_check L5-LNX-4 "Paramètres noyau durcis (sysctl)" lnx_sysctl fi # ------------------------------------------------------------------------------------------- 2. Rapport STAMP=$(date +%Y%m%d-%H%M%S) GENERATED_AT=$(date +%Y-%m-%dT%H:%M:%S%z | sed 's/\([0-9][0-9]\)$/:\1/') FILE="gentle-audit-$OS-$STAMP.json" DIR=$(pwd) echo "Résultat ($OS_VERSION)" echo "" printf '%s' "$SUMMARY" echo "" echo "Bilan : $N_OK ok, $N_KO ko, $N_UNK inconnu(s) sur $N_ALL points vérifiés." echo "inconnu = le script n'a pas pu conclure (droits, outil absent, réglage illisible) : vérifie ce point à la main avec le guide." if [ -e "$FILE" ]; then echo "Rapport non écrit : le fichier existe déjà ($DIR/$FILE)" exit 3 fi if [ ! -w . ]; then echo "Rapport non écrit : dossier courant inaccessible en écriture ($DIR)" exit 3 fi set -C if ! { printf '{\n' printf ' "schema": 1,\n' printf ' "os": "%s",\n' "$OS" printf ' "osVersion": "%s",\n' "$(json_esc "$OS_VERSION")" printf ' "generatedAt": "%s",\n' "$GENERATED_AT" printf ' "checks": [\n%s\n ]\n' "$JSON_CHECKS" printf '}\n' } > "$FILE" 2>/dev/null; then echo "Rapport non écrit : dossier courant inaccessible en écriture ($DIR)" exit 3 fi echo "Rapport écrit : $DIR/$FILE" exit 0