Inhaltsverzeichnis
Sigurnosne smjernice
Obavezni sigurnosni standardi za WvdS FPC RAD Suite, sukladni s KRITIS/NIS2.
OWASP Top 10 Prevencija
CWE-78: Command Injection
Rizik: Izvršavanje proizvoljnih naredbi putem manipuliranog inputa.
// ZABRANJENO - Nesigurno! Exec('cmd /c ' + UserInput); Shell('fpc ' + ProjectPath + ' ' + UserArgs); // ISPRAVNO - Parametrizirano Options.Shell := False; Args := TStringArray.Create; Args.Add(ProjectPath); Spawn('fpc', Args, Options);
Mjere:
- Uvijek koristiti
shell: false - Argumente predavati kao niz
- Validirati putanje prije korištenja
CWE-22: Path Traversal
Rizik: Pristup datotekama izvan dozvoljenog direktorija.
// ZABRANJENO - Nesigurno! FileName := BasePath + UserInput; ReadFile(FileName); // ISPRAVNO - Validacija function IsPathSafe(const ABasePath, AUserInput: string): Boolean; var ResolvedPath: string; begin // Ne dozvoliti .. 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));
Mjere:
- Blokirati
..sekvence - Normalizirati i validirati putanje
- Bazna putanja mora biti prefiks rezultantne putanje
CWE-20: Input Validation
Rizik: Nevaljani podaci dovode do neispravnog rada ili napada.
// ZABRANJENO - Bez validacije procedure CreateProject(const AName: string); begin MakeDir(AName); // Što ako AName = '../../../etc'? end; // ISPRAVNO - Validacija 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: Cleartext Credentials
Rizik: Credentials u kodu ili u logovima.
// ZABRANJENO - Credentials u kodu const API_TOKEN = 'ghp_xxxxxxxxxxxx'; DB_PASSWORD = 'secret123'; // ZABRANJENO - Credentials logirati LogDebug('Token: %s', [Token]); // ISPRAVNO - Environment Variables Token := GetEnvironmentVariable('GITHUB_TOKEN'); if Token = '' then raise EWvdSConfigError.Create(rsTokenNotConfigured); // ISPRAVNO - Maskirano logiranje LogDebug('Token configured: %s', [BoolToStr(Token <> '', True)]);
CWE-532: Log Injection
Rizik: Osjetljivi podaci vidljivi u logovima.
// ZABRANJENO LogInfo('User login: %s with password: %s', [User, Password]); LogDebug('API response: %s', [FullResponse]); // Može sadržavati tokene! // ISPRAVNO LogInfo('User login: %s', [User]); // Bez lozinke LogDebug('API response received, length: %d', [Length(Response)]);
CWE-79: Cross-Site Scripting (XSS)
Rizik: Ubacivanje Script-koda u WebViews.
// ZABRANJENO - Unescaped HTML WebView.Html := '<div>' + UserInput + '</div>'; // ISPRAVNO - 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 := '<div>' + EscapeHtml(UserInput) + '</div>';
CWE-20: WebView Message Whitelist
Rizik: Nepoznati Message-tipovi omogućuju napade na WebView-handlere.
// ZABRANJENO - Bez provjere Message-Type procedure HandleMessage(AMessage: TJSObject); var MsgType: string; begin MsgType := string(AMessage['type']); case MsgType of 'browse': HandleBrowse(AMessage); 'save': HandleSave(AMessage); end; end; // ISPRAVNO - S Whitelistom 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']); // Provjera Whiteliste PRVO if not IsAllowedMessageType(MsgType) then begin LogWarning(rsUnknownMessageType, [MsgType]); Exit; end; case MsgType of 'browse': HandleBrowse(AMessage); 'save': HandleSave(AMessage); // ... end; end;
Mjere:
- Definirati whitelistu sa svim dozvoljenim Message-tipovima
- Logirati i odbiti nepoznate tipove
- Ažurirati whitelistu kod novih funkcionalnosti
Defenzivni Node.js API-pozivi
Rizik: Node.js APIs mogu imati različite metode ovisno o kontekstu.
stdout.setEncoding nije funkcija ako Process nije ispravno inicijaliziran.
// ZABRANJENO - Direktni pozivi metoda bez provjere procedure SetupProcessHandlers; begin asm this.FProcess.stdout.setEncoding('utf8'); this.FProcess.stderr.setEncoding('utf8'); end; end; // ISPRAVNO - Defenzivne typeof-provjere 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;
Mjere:
typeof … === 'function' prije svakog poziva metode- Provjeriti postojanje objekta (
if (obj && obj.property)) - Graceful obrada nedostajućih APIs
Error Handling
Bez praznih Exception-Handlera
// ZABRANJENO try DoSomething; except // Gutanje greške end; // ISPRAVNO try DoSomething; except on E: ESpecificError do begin LogError(rsSpecificError, [E.Message]); // Obrada... end; on E: Exception do begin LogError(rsUnexpectedError, [E.ClassName, E.Message]); raise; // Ili odgovarajuće obraditi end; end;
Specifični Exception-tipovi
// ISPRAVNO - Specifične Exceptions 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
Debug-Logging zahtijeva dva uvjeta:
- Compile-Time:
{$IFDEF DEBUG} - Runtime:
–debugParametar
{$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}
Što smije biti logirano
| Dozvoljeno | Zabranjeno |
|---|---|
| Nazivi datoteka, putanje | Tokeni, API-Ključevi |
| Nazivi akcija | Lozinke |
| Numerički ID-evi | Session-ID-evi |
| Poruke o greškama | Potpuni Requests/Responses |
| Konfiguracijski ključevi | Konfiguracijske vrijednosti (osjetljive) |
Sigurne Default vrijednosti
// Sigurne standardne vrijednosti const DEFAULT_TIMEOUT = 30000; // 30 sekundi MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB MAX_RECURSION_DEPTH = 100; type TSpawnOptions = record Shell: Boolean; // Default: False (sigurno) Timeout: Integer; // Default: 30000 WorkingDir: string; Environment: TStringArray; end; function DefaultSpawnOptions: TSpawnOptions; begin Result.Shell := False; // VAŽNO: Bez Shell-a! Result.Timeout := DEFAULT_TIMEOUT; Result.WorkingDir := ''; Result.Environment := nil; end;
Code Review Checklista
Prije Mergea MORA se provjeriti:
SIGURNOST: [ ] Nema hardkodiranih Credentials [ ] Svi korisnički unosi validirani [ ] Shell-Injection nije moguć [ ] Path-Traversal nije moguć [ ] HTML se escapea u WebViews [ ] Nema osjetljivih podataka u logovima ERROR HANDLING: [ ] Nema praznih Exception-handlera [ ] Specifične Exceptions obrađene [ ] Greške se logiraju (bez osjetljivih podataka) KONFIGURACIJA: [ ] Sigurne Default vrijednosti korištene [ ] Timeouts definirani [ ] Limiti definirani (veličina datoteke, rekurzija)
Sigurnosni Audit
Redovita provjera s automatiziranim alatima:
wvds-lint Sigurnosne provjere
wvds-lint security --path sources/
Provjerava se:
- Exec/Shell-pozivi
- Path-operacije bez validacije
- Hardkodirani stringovi (potencijalni Credentials)
- Prazni Exception-handleri
- Log-pozivi s osjetljivim parametrima
Ručni Review
Za svaki novi Extension:
- Provjeriti sve Entry-Points
- Tracirati sve vanjske inpute
- Provjeriti WebView-komunikaciju
Incident Response
Pri otkrivanju sigurnosne ranjivosti:
- Ne objavljivati dok Patch nije spreman
- Issue na GitHubu s labelom „security“ (privatno)
- Razviti i testirati Patch
- Izdati novu verziju
- Objaviti Advisory