Varnostne smernice

Obvezujoči varnostni standardi za WvdS FPC RAD Suite, skladni s KRITIS/NIS2.

Te smernice niso pogajljive. Koda, ki jih krši, ne bo sprejeta.

Preprečevanje OWASP Top 10

CWE-78: Vstavljanje ukazov

Tveganje: Izvajanje poljubnih ukazov prek manipuliranega vnosa.

// PREPOVEDANO - Nevarno!
Exec('cmd /c ' + UserInput);
Shell('fpc ' + ProjectPath + ' ' + UserArgs);
 
// PRAVILNO - Parametrizirano
Options.Shell := False;
Args := TStringArray.Create;
Args.Add(ProjectPath);
Spawn('fpc', Args, Options);

Ukrepi:

  • Vedno uporabite shell: false
  • Argumente predajte kot polje
  • Validirajte poti pred uporabo

CWE-22: Prečkanje poti

Tveganje: Dostop do datotek izven dovoljenega imenika.

// PREPOVEDANO - Nevarno!
FileName := BasePath + UserInput;
ReadFile(FileName);
 
// PRAVILNO - Validacija
function IsPathSafe(const ABasePath, AUserInput: string): Boolean;
var
  ResolvedPath: string;
begin
  // Brez .. dovoljenih
  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));

Ukrepi:

  • Blokirajte sekvence ..
  • Normalizirajte in validirajte poti
  • Osnovna pot mora biti predpona rezultatne poti

CWE-20: Validacija vnosa

Tveganje: Neveljavni podatki povzročijo napačno delovanje ali napad.

// PREPOVEDANO - Brez validacije
procedure CreateProject(const AName: string);
begin
  MakeDir(AName);  // Kaj če je AName = '../../../etc'?
end;
 
// PRAVILNO - 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: Poverilnice v čistem besedilu

Tveganje: Poverilnice v kodi ali dnevnikih.

// PREPOVEDANO - Poverilnice v kodi
const
  API_TOKEN = 'ghp_xxxxxxxxxxxx';
  DB_PASSWORD = 'secret123';
 
// PREPOVEDANO - Beleženje poverilnic
LogDebug('Token: %s', [Token]);
 
// PRAVILNO - Okoljske spremenljivke
Token := GetEnvironmentVariable('GITHUB_TOKEN');
if Token = '' then
  raise EWvdSConfigError.Create(rsTokenNotConfigured);
 
// PRAVILNO - Maskirano beleženje
LogDebug('Token configured: %s', [BoolToStr(Token <> '', True)]);

CWE-532: Vstavljanje v dnevnike

Tveganje: Občutljivi podatki vidni v dnevnikih.

// PREPOVEDANO
LogInfo('User login: %s with password: %s', [User, Password]);
LogDebug('API response: %s', [FullResponse]);  // Lahko vsebuje žetone!
 
// PRAVILNO
LogInfo('User login: %s', [User]);  // Brez gesla
LogDebug('API response received, length: %d', [Length(Response)]);

CWE-79: Medspletno izvajanje skript (XSS)

Tveganje: Vstavljanje skriptne kode v WebViews.

// PREPOVEDANO - Neubežan HTML
WebView.Html := '<div>' + UserInput + '</div>';
 
// PRAVILNO - Ubežanje
function EscapeHtml(const AText: string): string;
begin
  Result := AText;
  Result := StringReplace(Result, '&', '&amp;', [rfReplaceAll]);
  Result := StringReplace(Result, '<', '&lt;', [rfReplaceAll]);
  Result := StringReplace(Result, '>', '&gt;', [rfReplaceAll]);
  Result := StringReplace(Result, '"', '&quot;', [rfReplaceAll]);
  Result := StringReplace(Result, '''', '&#39;', [rfReplaceAll]);
end;
 
WebView.Html := '<div>' + EscapeHtml(UserInput) + '</div>';

CWE-20: Beli seznam sporočil WebView

Tveganje: Neznani tipi sporočil omogočajo napade na obravnavalnike WebView.

Vsak obravnavalnik sporočil WebView MORA implementirati beli seznam dovoljenih tipov sporočil.
// PREPOVEDANO - Brez preverjanja tipa sporočila
procedure HandleMessage(AMessage: TJSObject);
var
  MsgType: string;
begin
  MsgType := string(AMessage['type']);
  case MsgType of
    'browse': HandleBrowse(AMessage);
    'save': HandleSave(AMessage);
  end;
end;
 
// PRAVILNO - Z belim seznamom
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']);
 
  // Preverjanje belega seznama NAJPREJ
  if not IsAllowedMessageType(MsgType) then
  begin
    LogWarning(rsUnknownMessageType, [MsgType]);
    Exit;
  end;
 
  case MsgType of
    'browse': HandleBrowse(AMessage);
    'save': HandleSave(AMessage);
    // ...
  end;
end;

Ukrepi:

  • Definirajte beli seznam z vsemi dovoljenimi tipi sporočil
  • Zabeležite in zavrnite neznane tipe
  • Pri novih funkcijah posodobite beli seznam

Obrambni klici Node.js API

Tveganje: Node.js API-ji imajo lahko različne metode glede na kontekst.

Problem iz prakse: stdout.setEncoding ni funkcija, če proces ni pravilno inicializiran.
// PREPOVEDANO - Neposredni klici metod brez preverjanja
procedure SetupProcessHandlers;
begin
  asm
    this.FProcess.stdout.setEncoding('utf8');
    this.FProcess.stderr.setEncoding('utf8');
  end;
end;
 
// PRAVILNO - Obrambna preverjanja typeof
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;

Ukrepi:

  • typeof … === 'function' pred vsakim klicem metode
  • Preverite obstoj objekta (if (obj && obj.property))
  • Manjkajoče API-je obravnavajte elegantno

Obravnava napak

Brez praznih obravnavalcev izjem

// PREPOVEDANO
try
  DoSomething;
except
  // Napaka se potlači
end;
 
// PRAVILNO
try
  DoSomething;
except
  on E: ESpecificError do
  begin
    LogError(rsSpecificError, [E.Message]);
    // Obravnava...
  end;
  on E: Exception do
  begin
    LogError(rsUnexpectedError, [E.ClassName, E.Message]);
    raise;  // Ali ustrezno obravnavajte
  end;
end;

Specifični tipi izjem

// PRAVILNO - Specifične izjeme
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;

Razhroščevalno beleženje

Razhroščevalno beleženje zahteva dva pogoja:

  1. Čas prevajanja: {$IFDEF DEBUG}
  2. Čas izvajanja: parameter –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}

Kaj se sme beležiti

Dovoljeno Prepovedano
Imena datotek, poti Žetoni, API ključi
Imena akcij Gesla
Numerični ID-ji ID-ji sej
Sporočila napak Popolni zahtevki/odgovori
Konfiguracijski ključi Konfiguracijske vrednosti (občutljive)

Varne privzete vrednosti

// Varne privzete vrednosti
const
  DEFAULT_TIMEOUT = 30000;  // 30 sekund
  MAX_FILE_SIZE = 10 * 1024 * 1024;  // 10 MB
  MAX_RECURSION_DEPTH = 100;
 
type
  TSpawnOptions = record
    Shell: Boolean;        // Privzeto: False (varno)
    Timeout: Integer;      // Privzeto: 30000
    WorkingDir: string;
    Environment: TStringArray;
  end;
 
function DefaultSpawnOptions: TSpawnOptions;
begin
  Result.Shell := False;  // POMEMBNO: Brez lupine!
  Result.Timeout := DEFAULT_TIMEOUT;
  Result.WorkingDir := '';
  Result.Environment := nil;
end;

Kontrolni seznam pregledov kode

Pred združitvijo JE TREBA preveriti:

VARNOST:
[ ] Brez zakodiranih poverilnic
[ ] Vsi uporabniški vnosi so validirani
[ ] Vstavljanje ukazov ni mogoče
[ ] Prečkanje poti ni mogoče
[ ] HTML je ubežan v WebViews
[ ] Brez občutljivih podatkov v dnevnikih

OBRAVNAVA NAPAK:
[ ] Brez praznih obravnavalcev izjem
[ ] Specifične izjeme obravnavane
[ ] Napake se beležijo (brez občutljivih podatkov)

KONFIGURACIJA:
[ ] Uporabljene varne privzete vrednosti
[ ] Časovne omejitve definirane
[ ] Omejitve definirane (velikost datoteke, rekurzija)

Varnostna revizija

Redna preverjanja z avtomatiziranimi orodji:

Varnostna preverjanja wvds-lint

wvds-lint security --path sources/

Preveri se:

  • Klici Exec/Shell
  • Operacije poti brez validacije
  • Zakodirani nizi (potencialne poverilnice)
  • Prazni obravnavalci izjem
  • Klici beleženja z občutljivimi parametri

Ročni pregled

Pri vsaki novi razširitvi:

  • Preverite vse vstopne točke
  • Sledite vsem zunanjim vnosom
  • Preverite komunikacijo WebView

Odziv na incidente

Ob odkritju varnostne ranljivosti:

  1. Ne objavite dokler popravek ni pripravljen
  2. Ustvarite issue na GitHub z oznako „security“ (zasebno)
  3. Razvijte in testirajte popravek
  4. Objavite novo različico
  5. Objavite obvestilo

Glejte tudi

Zuletzt geändert: dne 29.01.2026 ob 22:27