From bdb65a09ff247f8fb3d22913a3426a89fad1d177 Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 17 Mar 2026 15:33:36 +0100 Subject: [PATCH 1/4] fix: t-001 a t-005 correctif --- README.md | 15 ++++++++++- composables/useApiAuth.ts | 1 - server/api/backups.get.ts | 2 +- server/api/disk.get.ts | 44 +++++++++++++++----------------- server/middleware/auth-cookie.ts | 6 +++++ server/utils/ssh.ts | 7 +++-- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ad8e5ce..6fd8a2f 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,19 @@ Consequence visible : - si `API_SECRET_KEY` est vide, les appels API sont refusés avec `401 Unauthorized` - l'application web pose aussi un cookie HTTP-only via `server/middleware/auth-cookie.ts` pour réutiliser ce secret coté navigateur +## Securite + +Le comportement actuel du projet repose sur une hypothèse d'exposition très forte. + +- `server/middleware/auth-cookie.ts` pose automatiquement le cookie `api_auth_token` à tout visiteur qui charge l'interface web +- ce cookie permet ensuite d'accéder aux routes `/api/*` protégées par `API_SECRET_KEY` +- il n'existe pas de login utilisateur ni de contrôle d'identité distinct dans le dépôt + +Conséquence : + +- `Supervisor` doit être déployé uniquement sur un réseau de confiance, derrière un VPN, une restriction d'IP, un proxy d'authentification ou un autre contrôle d'accès externe +- si l'application est exposée publiquement sans protection supplémentaire, ce mécanisme ne constitue pas une authentification suffisante + ### SSH pour les backups Les fonctionnalités de backup utilisent `ssh` avec les options `BatchMode=yes` et `ConnectTimeout=5` dans `server/utils/ssh.ts`. Cela implique un accès sans saisie interactive de mot de passe. @@ -126,4 +139,4 @@ Usage : - `npm run generate` : généré une sortie statique si ce mode est compatible avec votre usage - `npm run preview` : prévisualisé le build Nuxt - `npm run lint` : execute ESLint -- `npm run lint:fix` : applique les corrections ESLint automatiques : collecte périodique CPU, mémoire et réseau \ No newline at end of file +- `npm run lint:fix` : applique les corrections ESLint automatiques : collecte périodique CPU, mémoire et réseau diff --git a/composables/useApiAuth.ts b/composables/useApiAuth.ts index d55e6e1..433e9be 100644 --- a/composables/useApiAuth.ts +++ b/composables/useApiAuth.ts @@ -42,7 +42,6 @@ export function useApiAuthHeader() { // Tous les appels frontend vers /api/* reutilisent ce header commun. return { - Authorization: `Bearer ${token}` } } diff --git a/server/api/backups.get.ts b/server/api/backups.get.ts index 135680c..3e87b28 100644 --- a/server/api/backups.get.ts +++ b/server/api/backups.get.ts @@ -7,7 +7,7 @@ import { import {process} from "std-env"; -const MAX_FILES_PER_FOLDER = Number(process.env.BACKUPS_MAX_FILES) +const MAX_FILES_PER_FOLDER = Math.max(1, Number(process.env.BACKUPS_MAX_FILES) || 50) const isSafeFolder = (value: string) => /^[a-zA-Z0-9._-]+$/.test(value) diff --git a/server/api/disk.get.ts b/server/api/disk.get.ts index 54554f6..5341c9a 100644 --- a/server/api/disk.get.ts +++ b/server/api/disk.get.ts @@ -1,10 +1,16 @@ -import { exec } from "child_process" +import { execFile } from "node:child_process" type DiskSource = { key: "remote" | "local" label: string } +type CommandSpec = { + command: string + args: string[] + cwd?: string +} + const diskSources: DiskSource[] = [ { key: "remote", @@ -16,33 +22,28 @@ const diskSources: DiskSource[] = [ } ] -function getDefaultCommand(source: DiskSource) { +function getCommand(source: DiskSource): CommandSpec { const localScriptDir = process.env.DISK_LOCAL_SCRIPT_DIR || "/home/malio/Malio-ops/CheckStorage" const remoteHost = process.env.DISK_REMOTE_HOST || "malio-b" const remoteScriptDir = process.env.DISK_REMOTE_SCRIPT_DIR || "/home/malio-b/Malio-ops/CheckStorage" if (source.key === "local") { - return `cd ${localScriptDir} && bash check-storage.sh` + return { + command: "bash", + args: ["check-storage.sh"], + cwd: localScriptDir + } } - return `ssh ${remoteHost} "cd ${remoteScriptDir} && ./check-storage.sh"` + return { + command: "ssh", + args: [remoteHost, `cd ${remoteScriptDir} && ./check-storage.sh`] + } } -function getEnvCommand(source: DiskSource) { - const envKey = `DISK_COMMAND_${source.key.toUpperCase()}` - const legacyEnvKey = - source.key === "remote" ? "DISK_REMOTE_COMMAND" : source.key === "local" ? "DISK_LOCAL_COMMAND" : "" - - return ( - process.env[envKey] || - (legacyEnvKey ? process.env[legacyEnvKey] : undefined) || - getDefaultCommand(source) - ) -} - -function runShellCommand(command: string): Promise { +function runCommand({ command, args, cwd }: CommandSpec): Promise { return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { + execFile(command, args, { cwd }, (error, stdout, stderr) => { if (error) { reject(stderr || error.message) return @@ -56,12 +57,7 @@ export default defineEventHandler(async () => { const results = await Promise.all( diskSources.map(async (source) => { try { - const envCommand = getEnvCommand(source) - if (!envCommand) { - throw new Error(`Commande disque manquante pour ${source.key}`) - } - - const output = await runShellCommand(envCommand) + const output = await runCommand(getCommand(source)) return { key: source.key, label: source.label, diff --git a/server/middleware/auth-cookie.ts b/server/middleware/auth-cookie.ts index b272b1a..9b7aec5 100644 --- a/server/middleware/auth-cookie.ts +++ b/server/middleware/auth-cookie.ts @@ -1,3 +1,9 @@ +// SECURITE: +// Ce middleware pose automatiquement le cookie d'authentification pour tout +// visiteur de l'interface web. Ce comportement repose sur l'hypothèse que +// Supervisor n'est exposé qu'à un réseau de confiance ou derrière un contrôle +// d'accès externe. Si l'application devient publiquement accessible, ce +// mécanisme ne constitue pas une authentification utilisateur. export default defineEventHandler((event) => { const path = event.path || event.node.req.url || "" diff --git a/server/utils/ssh.ts b/server/utils/ssh.ts index 614c19e..42cd129 100644 --- a/server/utils/ssh.ts +++ b/server/utils/ssh.ts @@ -1,4 +1,4 @@ -import { execFile } from "node:child_process" +import {execFile} from "node:child_process" import {process} from "std-env"; import folderMap from "#server/config/backup-folders.json"; @@ -10,11 +10,14 @@ export const FOLDER_MAP = folderMap as Record export const shellQuote = (value: string) => `'${value.replace(/'/g, `'\\''`)}'` export function runSsh(command: string): Promise { + if (!REMOTE_HOST) { + return Promise.reject(new Error("BACKUPS_REMOTE_HOST is not configured")) + } return new Promise((resolve, reject) => { execFile( "ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", REMOTE_HOST, command], - { maxBuffer: 10 * 1024 * 1024 }, + {maxBuffer: 10 * 1024 * 1024}, (error, stdout, stderr) => { if (error) { reject(stderr || error.message) From c12387ac947cde677e78fe77d914a904795d404c Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 18 Mar 2026 09:00:11 +0100 Subject: [PATCH 2/4] fix: t-005 a t-0029 correctif --- assets/css/main.css | 1 - components/BackupRun.vue | 23 +++-------------------- components/SystemMetricsChart.vue | 2 +- layouts/default.vue | 1 - nuxt.config.ts | 6 ++++-- package-lock.json | 8 +++++--- package.json | 6 ++++-- pages/index.vue | 9 --------- public/robots.txt | 2 +- server/api/backups.get.ts | 4 +--- server/api/discord/messages.get.ts | 7 ++++--- server/api/download-latest.get.ts | 8 +++++--- server/api/download.get.ts | 8 +++----- server/api/upload.post.ts | 3 ++- server/api/version-status.get.ts | 10 +++++++++- server/utils/ssh.ts | 6 +++--- 16 files changed, 45 insertions(+), 59 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index d7aaddf..f0b5e8c 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -15,7 +15,6 @@ --color-m-success: rgb(var(--m-success)); --color-m-accent: rgb(var(--m-accent)); --color-m-warning: rgb(var(--m-warning)); - --color-m-succes: rgb(var(--m-success)); --font-display: "Outfit", system-ui, sans-serif; --font-mono: "JetBrains Mono", "Fira Code", monospace; } diff --git a/components/BackupRun.vue b/components/BackupRun.vue index 639dce8..6252d7d 100644 --- a/components/BackupRun.vue +++ b/components/BackupRun.vue @@ -77,7 +77,6 @@