Jesu li podaci mojih kupaca i dućan sigurni od curenja i napada?
Trgovci najčešće pitaju istu stvar prije nego što povjere platformi svoje podatke i podatke kupaca: što se događa ako netko pokuša pristupiti tuđem dućanu, pretjerano slati zahtjeve ili iskoristiti neku rupu? WebShopHR je građen tako da se sigurnost ne oslanja na jedan mehanizam, nego na slojeve koji se nadopunjuju od baze do rubnog poslužitelja.
Svaki dućan vidi samo svoje retke u bazi
Baza podataka koristi PostgreSQL Row-Level Security (pravilo koje baza sama provjerava na svakom retku, prije nego aplikacija dobije rezultat) na svim tablicama koje pripadaju tenantu (dućanu kojem redak pripada). Svaka transakcija pokreće se s postavljenim tenant kontekstom, pa čak i ako bi neki upit negdje procurio bez tog konteksta, RLS bi vratio prazan skup rezultata.
Aplikacijska baza rola nema mogućnost zaobilaženja RLS-a, dok vlasnička rola služi isključivo za migracije. Postoji i poseban platformski kontekst za administrativne operacije nad popisom dućana i domenama, pa i ti putevi ne rade kao običan tenant pristup.
Tajne se ne vraćaju kroz API
Kad trgovac ili CMS traži postavke, odgovor nikad ne sadrži sirove tajne poput Stripe, Corvus, WSPay ili Arges API ključeva. Umjesto toga vraćaju se samo zastavice jesu li te tajne postavljene.
Isto vrijedi i za odlazne webhookove: tajna se generira prilikom kreiranja i prikazuje samo u tom trenutku. Svaki sljedeći poziv vidi samo to da je tajna postavljena, ali je ne može pročitati. Time se štiti od curenja čak i ako bi netko već bio autenticiran unutar CMS-a.

Sigurnosna zaglavlja na svim odgovorima
Svaki HTTP odgovor nosi standardna sigurnosna zaglavlja: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, strogi Content-Security-Policy za skripte, te Strict-Transport-Security (djeluje samo preko HTTPS-a). Pravilo dopušta samo vlastite i unaprijed navedene izvore. Dva ugrađena dijela koda (kartično plaćanje i prekidač za način prikaza) odobrena su pojedinačno, po vlastitom kriptografskom otisku.
Oznaka koja bi inače dopustila svaki ugrađeni kod ostaje u pravilu samo radi starijih preglednika. Svaki moderni preglednik koji razumije odobravanje po otisku tu oznaku zanemaruje, pa zaštita za nove preglednike ostaje puna dok se stranica na starima ne razbije.
CSRF na svim javnim formama koje mijenjaju stanje
Košarica, registracija, recenzije, napuštene košarice i druge forme na javnom dijelu dućana zaštićene su dvostrukim kolačićem. Poslužitelj generira dugi slučajni token, sprema ga u HttpOnly kolačić, a isti token mora biti prisutan u skrivenom polju forme.
Usporedba se radi konstantnim vremenom, a stranice koje se serviraju iz dijeljenog predmemoriranog cachea koriste zamjenski token koji se zamjenjuje tek kad odgovor ide kupcu. Time se spriječava da jedan kupac dobije tuđi token.
Rate limit prilagođen paketu
Brza zloupotreba ograničena je na kritičnim tokovima: naplata, prijava, magic link, registracija, recenzije, pokušaji unosa koda poklon bona, napuštene košarice, AI chat, izvoz i AI opisi. Ograničenja se skaliraju prema paketu dućana, pa Pro i Enterprise trgovci dobivaju veći propuštaj nego Free paket. Limite drži vanjski predmemorirani spremnik, a ne lokalno stanje poslužitelja.
Stvarna IP adresa kupca i zaštita od lažnog X-Forwarded-For
Iza povjerljivog proxyja sustav čita posljednji unos u X-Forwarded-For zaglavlju, a ne prvi, pa napadač ne može dodati vlastitu lažnu adresu na početak liste i dobiti svježi rate-limit bucket. Bez povjerenja proxyja to se zaglavlje u potpunosti zanemaruje i koristi se direktna TCP adresa klijenta. Svaki unos se provjeri kao valjana IP adresa prije nego što se upotrijebi.
Odlazni webhookovi ne mogu pogoditi interne resurse
Kad trgovac postavi odlazni webhook, poslužitelj prije poziva razrješava ciljnu adresu i odbija sve privatne, loopback, link-local i druge zabranjene raspone. Slijeđenje preusmjerenja je onemogućeno, a stvarna veza ide prema baš onoj IP adresi koja je provjerena, što zatvara rupu DNS rebindinga. Ograničen je i vremenski okvir unutar kojeg primatelj mora odgovoriti, a pokušaji se ponavljaju samo do unaprijed definiranog broja.
Slike se serviraju sigurnim ključevima
Javni proxy za slike prihvaća samo ključeve koji imaju točnu strukturu slike artikla i provjerava da tenant (dućan) iz ključa postoji i da je aktivan. Time se sprječava da netko preko iste rute dohvati druge objekte, uključujući digitalne proizvode.
Kad se koristi imgproxy, URL-ovi su potpisani HMAC-SHA256 (kriptografskim potpisom koji sprječava sastavljanje proizvoljnih URL-ova), a sam imgproxy podešen je da prima isključivo objekte iz vlastitog spremnika. Ako imgproxy treba biti uključen, a nedostaju potpisni ključevi, aplikacija se odbija pokrenuti.
Lozinke kupaca i konstantno vrijeme prijave
Lozinke kupaca u dućanu heširaju se Argon2id algoritmom (posebno sporim za pogađanje lozinke, otpornim i na jaka računala) prema preporukama OWASP-a. Prijava troši jednako vremena bez obzira na to postoji li e-mail adresa u bazi: ako kupac ne postoji, sustav ipak izvrši heširanje nad lažnim hešem iste cijene. To sprječava napadača da mjerenjem vremena odgovora otkrije koje e-mail adrese imaju račun.
Fail-closed je zadani način rada
Cijela aplikacija po defaultu radi u produkcijskom načinu rada, a dev olakšice se uključuju tek eksplicitnim znakom. Nedostajuća konfiguracija ne vodi na prikladniji dev default, nego gasi značajku: nema kartičnog plaćanja bez ključeva, nema imgproxyja bez ključeva, nema registracije bez Zitadel pristupnog tokena. Interne rute poput konfiguracije za Traefik i metrika zaštićene su dodatnim unutarnjim tokenom izvan dev okruženja.
Slojevit pristup znači da se sigurnosne kontrole međusobno podupiru: čak i kad bi jedan sloj poklekao, sljedeći bi zaustavio napad.
Više o temi: Sigurnost i infrastruktura.
Česta pitanja.
Kako WebShopHR sprječava da jedan dućan slučajno vidi podatke drugog?
Row-Level Security na razini PostgreSQL baze provjerava tenanta na svakoj transakciji. Aplikacijska baza rola nema mogućnost zaobići RLS, a poseban platformski kontekst postoji samo za administrativne operacije nad popisom dućana.
Vraća li API ikad stvarne API ključeve (Stripe, WSPay, Arges) natrag u odgovoru?
Ne. Odgovor uvijek nosi samo zastavicu je li tajna postavljena, nikad sirovu vrijednost: isto vrijedi i za tajne odlaznih webhookova, koje se u cijelosti prikažu samo jednom, u trenutku kreiranja.
Kako se čuvaju lozinke kupaca?
Argon2id algoritmom prema OWASP preporukama. Prijava troši jednako vremena bez obzira postoji li e-mail u bazi, jer sustav i za nepostojeći račun izvrši heširanje lažnog hasha iste cijene. To sprječava otkrivanje valjanih e-mailova mjerenjem vremena odgovora.
Može li netko zloupotrijebiti moj webhook endpoint da skenira moju internu mrežu?
Ne s WebShopHR strane odlaznih webhookova: poslužitelj prije poziva odbija privatne, loopback i link-local IP raspone, ne slijedi preusmjerenja i zaključava se na provjerenu IP adresu, što zatvara i rupu DNS rebindinga.
Je li aplikacija sigurna po defaultu ili treba ručno uključivati zaštitu?
Fail-closed je zadano ponašanje: nedostajuća konfiguracija (imgproxy ključevi, plaćanje, Zitadel token) gasi značajku umjesto da padne na manje siguran dev default.