====== Linee guida sulla sicurezza ====== Standard di sicurezza vincolanti per la suite WvdS FPC RAD, conformi a KRITIS/NIS2. Queste linee guida sono **non negoziabili**. Il codice che le viola non sara accettato. ===== Prevenzione OWASP Top 10 ===== ==== CWE-78: Command Injection ==== **Rischio:** Esecuzione di comandi arbitrari tramite input manipolato. // VIETATO - Non sicuro! Exec('cmd /c ' + UserInput); Shell('fpc ' + ProjectPath + ' ' + UserArgs); // CORRETTO - Parametrizzato Options.Shell := False; Args := TStringArray.Create; Args.Add(ProjectPath); Spawn('fpc', Args, Options); **Misure:** * Usare sempre ''shell: false'' * Passare argomenti come array * Validare i percorsi prima dell'uso ==== CWE-22: Path Traversal ==== **Rischio:** Accesso a file al di fuori della directory consentita. // VIETATO - Non sicuro! FileName := BasePath + UserInput; ReadFile(FileName); // CORRETTO - Validazione function IsPathSafe(const ABasePath, AUserInput: string): Boolean; var ResolvedPath: string; begin // Non permettere .. if Pos('..', AUserInput) > 0 then Exit(False); ResolvedPath := ExpandFileName(Concat(ABasePath, AUserInput)); Result := Pos(ABasePath, ResolvedPath) = 1; end; if IsPathSafe(BasePath, UserInput) then ReadFile(Concat(BasePath, UserInput)); **Misure:** * Bloccare sequenze ''..'' * Normalizzare e validare i percorsi * Il percorso base deve essere prefisso del percorso risultante ==== CWE-20: Input Validation ==== **Rischio:** Dati non validi causano malfunzionamenti o attacchi. // VIETATO - Nessuna validazione procedure CreateProject(const AName: string); begin MakeDir(AName); // E se AName = '../../../etc'? end; // CORRETTO - Validazione function ValidateProjectName(const AName: string; out AError: string): Boolean; const ALLOWED_CHARS = ['a'..'z', 'A'..'Z', '0'..'9', '_', '-']; MAX_LENGTH = 64; var I: Integer; begin Result := False; if AName = '' then begin AError := rsProjectNameEmpty; Exit; end; if Length(AName) > MAX_LENGTH then begin AError := Format(rsProjectNameTooLong, [MAX_LENGTH]); Exit; end; for I := 1 to Length(AName) do if not (AName[I] in ALLOWED_CHARS) then begin AError := Format(rsProjectNameInvalidChar, [AName[I]]); Exit; end; Result := True; end; ==== CWE-316: Credenziali in chiaro ==== **Rischio:** Credenziali nel codice o nei log. // VIETATO - Credenziali nel codice const API_TOKEN = 'ghp_xxxxxxxxxxxx'; DB_PASSWORD = 'secret123'; // VIETATO - Loggare credenziali LogDebug('Token: %s', [Token]); // CORRETTO - Variabili d'ambiente Token := GetEnvironmentVariable('GITHUB_TOKEN'); if Token = '' then raise EWvdSConfigError.Create(rsTokenNotConfigured); // CORRETTO - Logging mascherato LogDebug('Token configurato: %s', [BoolToStr(Token <> '', True)]); ==== CWE-532: Log Injection ==== **Rischio:** Dati sensibili visibili nei log. // VIETATO LogInfo('Login utente: %s con password: %s', [User, Password]); LogDebug('Risposta API: %s', [FullResponse]); // Puo contenere token! // CORRETTO LogInfo('Login utente: %s', [User]); // Nessuna password LogDebug('Risposta API ricevuta, lunghezza: %d', [Length(Response)]); ==== CWE-79: Cross-Site Scripting (XSS) ==== **Rischio:** Iniezione di codice script nelle WebView. // VIETATO - HTML non escapato WebView.Html := '
' + UserInput + '
'; // CORRETTO - Escaping function EscapeHtml(const AText: string): string; begin Result := AText; Result := StringReplace(Result, '&', '&', [rfReplaceAll]); Result := StringReplace(Result, '<', '<', [rfReplaceAll]); Result := StringReplace(Result, '>', '>', [rfReplaceAll]); Result := StringReplace(Result, '"', '"', [rfReplaceAll]); Result := StringReplace(Result, '''', ''', [rfReplaceAll]); end; WebView.Html := '
' + EscapeHtml(UserInput) + '
';
==== CWE-20: Whitelist messaggi WebView ==== **Rischio:** Tipi di messaggio sconosciuti permettono attacchi ai gestori WebView. Ogni gestore messaggi WebView DEVE implementare una whitelist dei tipi di messaggio consentiti. // VIETATO - Nessun controllo tipo messaggio procedure HandleMessage(AMessage: TJSObject); var MsgType: string; begin MsgType := string(AMessage['type']); case MsgType of 'browse': HandleBrowse(AMessage); 'save': HandleSave(AMessage); end; end; // CORRETTO - Con whitelist const ALLOWED_MESSAGE_TYPES: array[0..6] of string = ( 'browse', 'save', 'cancel', 'validatePath', 'autoDetect', 'config', 'pathSelected' ); function IsAllowedMessageType(const AType: string): Boolean; var I: Integer; begin Result := False; for I := Low(ALLOWED_MESSAGE_TYPES) to High(ALLOWED_MESSAGE_TYPES) do if ALLOWED_MESSAGE_TYPES[I] = AType then Exit(True); end; procedure HandleMessage(AMessage: TJSObject); var MsgType: string; begin MsgType := string(AMessage['type']); // Controllo whitelist PRIMA if not IsAllowedMessageType(MsgType) then begin LogWarning(rsUnknownMessageType, [MsgType]); Exit; end; case MsgType of 'browse': HandleBrowse(AMessage); 'save': HandleSave(AMessage); // ... end; end; **Misure:** * Definire whitelist con tutti i tipi di messaggio consentiti * Loggare e rifiutare tipi sconosciuti * Aggiornare la whitelist per nuove funzionalita ==== Chiamate API Node.js difensive ==== **Rischio:** Le API Node.js possono avere metodi diversi a seconda del contesto. Problema dalla pratica: ''stdout.setEncoding'' non e una funzione se il processo non e inizializzato correttamente. // VIETATO - Chiamate metodo dirette senza controllo procedure SetupProcessHandlers; begin asm this.FProcess.stdout.setEncoding('utf8'); this.FProcess.stderr.setEncoding('utf8'); end; end; // CORRETTO - Controlli typeof difensivi procedure SetupProcessHandlers; begin asm if (this.FProcess && this.FProcess.stdout) { if (typeof this.FProcess.stdout.setEncoding === 'function') { this.FProcess.stdout.setEncoding('utf8'); } if (typeof this.FProcess.stdout.on === 'function') { this.FProcess.stdout.on('data', this.HandleStdout.bind(this)); } } if (this.FProcess && this.FProcess.stderr) { if (typeof this.FProcess.stderr.setEncoding === 'function') { this.FProcess.stderr.setEncoding('utf8'); } if (typeof this.FProcess.stderr.on === 'function') { this.FProcess.stderr.on('data', this.HandleStderr.bind(this)); } } end; end; **Misure:** * ''typeof ... === 'function''' prima di ogni chiamata metodo * Verificare esistenza oggetto (''if (obj && obj.property)'') * Gestire gracefully API mancanti ===== Gestione errori ===== ==== Nessun handler eccezione vuoto ==== // VIETATO try DoSomething; except // Ingoiare l'errore end; // CORRETTO try DoSomething; except on E: ESpecificError do begin LogError(rsSpecificError, [E.Message]); // Gestione... end; on E: Exception do begin LogError(rsUnexpectedError, [E.ClassName, E.Message]); raise; // O gestire appropriatamente end; end; ==== Tipi eccezione specifici ==== // CORRETTO - Eccezioni specifiche try Result := DoOperation; except on E: EFileNotFoundException do HandleFileNotFound(E.FileName); on E: EAccessDenied do HandleAccessDenied(E.Path); on E: ENetworkError do HandleNetworkError(E.Url); on E: Exception do HandleUnexpectedError(E); end; ===== Debug logging ===== Il debug logging richiede **due condizioni**: - **Compile-time:** ''{$IFDEF DEBUG}'' - **Runtime:** parametro ''--debug'' {$IFDEF DEBUG} var DebugLogFile: TextFile; DebugEnabled: Boolean; procedure InitDebugLogging; begin DebugEnabled := ParamStr(1) = '--debug'; if DebugEnabled then begin AssignFile(DebugLogFile, Format('debug-%s.log', [FormatDateTime('yymmddhhnnss', Now)])); Rewrite(DebugLogFile); end; end; procedure LogDebugTrace(const AMessage: string; const AArgs: array of const); begin if not DebugEnabled then Exit; WriteLn(DebugLogFile, Format('[%s] %s', [FormatDateTime('hh:nn:ss.zzz', Now), Format(AMessage, AArgs)])); Flush(DebugLogFile); end; {$ENDIF} ==== Cosa si puo loggare ==== ^ Consentito ^ Vietato ^ | Nomi file, percorsi | Token, API key | | Nomi azioni | Password | | ID numerici | ID sessione | | Messaggi errore | Request/response completi | | Chiavi configurazione | Valori configurazione (sensibili) | ===== Default sicuri ===== // Valori default sicuri const DEFAULT_TIMEOUT = 30000; // 30 secondi MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB MAX_RECURSION_DEPTH = 100; type TSpawnOptions = record Shell: Boolean; // Default: False (sicuro) Timeout: Integer; // Default: 30000 WorkingDir: string; Environment: TStringArray; end; function DefaultSpawnOptions: TSpawnOptions; begin Result.Shell := False; // IMPORTANTE: Nessuna shell! Result.Timeout := DEFAULT_TIMEOUT; Result.WorkingDir := ''; Result.Environment := nil; end; ===== Checklist code review ===== Prima del merge DEVE essere verificato: SICUREZZA: [ ] Nessuna credenziale hardcoded [ ] Tutti gli input utente validati [ ] Nessuna shell injection possibile [ ] Nessun path traversal possibile [ ] HTML escapato nelle WebView [ ] Nessun dato sensibile nei log GESTIONE ERRORI: [ ] Nessun handler eccezione vuoto [ ] Eccezioni specifiche gestite [ ] Errori loggati (senza dati sensibili) CONFIGURAZIONE: [ ] Default sicuri usati [ ] Timeout definiti [ ] Limiti definiti (dimensione file, ricorsione) ===== Audit sicurezza ==== Verifica regolare con strumenti automatizzati: ==== Controlli sicurezza wvds-lint ==== wvds-lint security --path sources/ Viene verificato: * Chiamate Exec/Shell * Operazioni path senza validazione * Stringhe hardcoded (potenziali credenziali) * Handler eccezione vuoti * Chiamate log con parametri sensibili ==== Review manuale ==== Per ogni nuova estensione: * Verificare tutti gli entry point * Tracciare tutti gli input esterni * Verificare comunicazione WebView ===== Risposta agli incidenti ===== In caso di scoperta di una vulnerabilita: - **Non pubblicare** finche la patch non e pronta - Issue su GitHub con label "security" (privata) - Sviluppare e testare la patch - Rilasciare nuova versione - Pubblicare advisory ===== Vedi anche ===== * [[.:code-konventionen|Convenzioni del codice]] * [[.:debugging|Debugging]] * [[.:testing|Testing]] * [[https://owasp.org/www-project-top-ten/|OWASP Top 10]]