// 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 := '' + UserInput + '';
// 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 := '' + EscapeHtml(UserInput) + '';
==== 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.
// 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:** ''--debug'' Parametar
{$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
===== Vidi također =====
* [[.:code-konventionen|Konvencije koda]]
* [[.:debugging|Debugging]]
* [[.:testing|Testing]]
* [[https://owasp.org/www-project-top-ten/|OWASP Top 10]]