Gitea - lien « Code source » hardcodé (REPO_SOURCE_URL) vers le dépôt, toujours visible - retrait complet du réglage configurable (api.ts, route+store settings, SettingsView, i18n, help, tests) Découverte des dépôts - aucune racine de scan par défaut → pas de scan au premier démarrage (clean install) - corrige la découverte des dépôts à l'INTÉRIEUR d'une racine qui est elle-même un repo (depth 0 = conteneur de scan, on descend ; depth > 0 = feuille) Sécurité « enterprise-deployable » - en-têtes HTTP durcis (CSP, X-Frame-Options, nosniff, Referrer-Policy, HSTS conditionnel, no-store API), header Server retiré - permissions DB 0o600 / dossier de données 0o700 ; error handler sanitisé ; garde Content-Type sur les mutations - chiffrement au repos AES-256-GCM des secrets (server_secret, clé privée VAPID) via SecretBox - journal d'audit (migration #7) + endpoint /audit-logs + RGPD export/effacement + UI Réglages - SECURITY.md, docs/ENTERPRISE_DEPLOYMENT.md, sections README EN/FR, SBOM CycloneDX en CI CI - pack-smoke packe depuis le contexte du package (cd packages/server) au lieu de « npm pack -w » : corrige l'embarquement de @arboretum/shared dans le tarball Purge des données personnelles du dépôt public - suppression de spikes/s4-discovery/result.json, reformulation du VERDICT - chemin de test générique, anonymisation des 5 fixtures de dialogues
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
// Réglages de la découverte auto des repos, persistés dans la table `settings` (clé/valeur).
|
||
// Frontière de sécurité : ces clés sont NON sensibles et n'entrent dans l'allow-list du PATCH
|
||
// /api/v1/settings que via les validateurs ci-dessous. Aucun secret ne transite par ici.
|
||
import { getSetting } from '../db/index.js';
|
||
import type { Db } from '../db/index.js';
|
||
import { isSafeAbsolutePath } from './git.js';
|
||
|
||
export const SCAN_ROOTS_KEY = 'scan_roots';
|
||
export const SCAN_INTERVAL_KEY = 'scan_interval_min';
|
||
|
||
/** Intervalle par défaut du re-scan périodique (minutes). 0 = désactivé. */
|
||
export const DEFAULT_SCAN_INTERVAL_MIN = 5;
|
||
/** Borne haute de l'intervalle (24 h) et nombre maximal de racines. */
|
||
export const MAX_SCAN_INTERVAL_MIN = 1440;
|
||
export const MAX_SCAN_ROOTS = 16;
|
||
|
||
/**
|
||
* Racines à scanner. Défaut : AUCUNE racine → aucun scan (clean install).
|
||
* L'utilisateur ajoute ses racines via Réglages → Découverte. Lecture tolérante (JSON malformé → []).
|
||
*/
|
||
export function readScanRoots(db: Db): string[] {
|
||
const raw = getSetting(db, SCAN_ROOTS_KEY);
|
||
if (!raw) return [];
|
||
return normalizeScanRoots(safeParse(raw)) ?? [];
|
||
}
|
||
|
||
/** Intervalle périodique en minutes (0 = désactivé). Défaut DEFAULT_SCAN_INTERVAL_MIN. */
|
||
export function readScanIntervalMin(db: Db): number {
|
||
const raw = getSetting(db, SCAN_INTERVAL_KEY);
|
||
if (raw === null) return DEFAULT_SCAN_INTERVAL_MIN;
|
||
const n = Number(raw);
|
||
return Number.isInteger(n) && n >= 0 && n <= MAX_SCAN_INTERVAL_MIN ? n : DEFAULT_SCAN_INTERVAL_MIN;
|
||
}
|
||
|
||
/**
|
||
* Valide/normalise une liste de racines : tableau de chemins absolus normalisés (isSafeAbsolutePath),
|
||
* jamais `/` (scan catastrophique), dédupliqués, ≤ MAX_SCAN_ROOTS. Retourne null si invalide (⇒ 400).
|
||
* Une liste vide est valide (revient au défaut côté lecture).
|
||
*/
|
||
export function normalizeScanRoots(raw: unknown): string[] | null {
|
||
if (!Array.isArray(raw)) return null;
|
||
if (raw.length > MAX_SCAN_ROOTS) return null;
|
||
const out: string[] = [];
|
||
for (const item of raw) {
|
||
if (typeof item !== 'string') return null;
|
||
const p = item.trim();
|
||
if (!isSafeAbsolutePath(p) || p === '/') return null;
|
||
if (!out.includes(p)) out.push(p);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/** Valide un intervalle (entier 0–MAX_SCAN_INTERVAL_MIN). Retourne null si invalide. */
|
||
export function normalizeScanIntervalMin(raw: unknown): number | null {
|
||
if (typeof raw !== 'number' || !Number.isInteger(raw)) return null;
|
||
if (raw < 0 || raw > MAX_SCAN_INTERVAL_MIN) return null;
|
||
return raw;
|
||
}
|
||
|
||
function safeParse(raw: string): unknown {
|
||
try {
|
||
return JSON.parse(raw);
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|