← COXSWAIN · coxswain.ro/securitate
Document model · date fictive · șablon COXSWAIN - nu reprezintă un client real
Confidențial
COXSWAINAI FLEET · HUMAN COMMAND
Raport de evaluare de securitate

Security Code Review
Platformă rezervări & comenzi

Client
TERASA VERDE S.R.L. (fictiv)
Aplicație
API Node.js / Express + React
Perioada evaluării
2-4 septembrie 2026
Versiune raport
1.0 · final
Auditor
Andrei Stoica · COXSWAIN
Constatări
10 reale · 2 critice
SECȚIUNEA 1

Rezumat executiv

Această pagină e pentru cine ia decizia, nu pentru echipa tehnică. Fără jargon.

Am analizat codul aplicației de rezervări și comenzi a TERASA VERDE și am identificat 10 probleme reale de securitate, dintre care două critice care necesită acțiune în 24 de ore. Aplicația funcționează corect din punct de vedere al utilizatorului, dar are lacune tipice unei dezvoltări rapide: date personale accesibile între conturi, chei de producție expuse și o cale de acces neautorizat la baza de date.

2
Critice
3
Ridicate
4
Medii
1
Scăzute
34
Zgomot triat

Cele trei lucruri de rezolvat acum

#ProblemaDe ce e urgent
CriticComenzile altor clienți sunt vizibile (COX-01)Orice client logat poate citi comenzile oricui schimbând un număr în adresă. Date personale expuse - problemă GDPR imediată.
CriticChei de producție în cod (COX-02)Parola bazei de date și cheia de plăți live Stripe sunt urcate în repository. Sunt compromise din acest moment; trebuie rotite azi.
RidicatCale de injecție în baza de date (COX-03)Câmpul de căutare permite manipularea directă a interogărilor SQL. Poate expune toată baza.

Postura generală

Problemele nu sunt semne de neglijență, ci de viteză: cod scris să funcționeze repede, fără trecerea de securitate. Toate cele 10 sunt reparabile, majoritatea în ore, nu zile. Estimare de remediere: ~3-5 zile de lucru pentru echipa voastră, împărțite pe urgențe în Secțiunea 4. După remediere și o reverificare, aplicația ajunge la un nivel de securitate rezonabil pentru producție.

Ce înseamnă „34 zgomot triat"
Uneltele automate au ridicat 47 de alerte. Le-am verificat manual pe toate. 34 au fost false pozitive (tipare care par probleme dar nu sunt). Raportez doar cele 10 reale. Trierea asta e diferența dintre un raport pe care îl poți acționa și o listă de 300 de avertismente pe care nu o citește nimeni. Un exemplu de fals pozitiv, cu explicație, în Anexă.

SECȚIUNEA 2

Metodologie & scop

Ce a fost analizat

  • Codul sursă al API-ului (Node.js / Express) - autentificare, autorizare, acces la date, procesare plăți, validare
  • Configurarea proiectului și gestionarea secretelor
  • Dependențele (package.json + lockfile)
  • Frontend-ul React - doar la nivel de fluxuri de date sensibile, nu UI

Cum

Analiză statică asistată de unelte, urmată de verificare manuală a fiecărei constatări. Uneltele găsesc tipare; judecata decide ce e real, cât de grav e și cum se repară în contextul acestei aplicații.

UnealtăRol
Semgrep CEAnaliză statică, tipare de vulnerabilități (autorizare, injecție, cripto)
gitleaksSecrete în cod și în istoricul git
TrivyVulnerabilități cunoscute în dependențe (CVE)
Verificare manualăTrierea rezultatelor, logica de business, contextul

Standardul de referință

Constatările sunt clasificate față de OWASP ASVS 4.0 și OWASP API Security Top 10 (2023). Gravitatea reflectă impact × probabilitate în contextul acestei aplicații.

Ce NU a fost în scop

  • Test de penetrare activ (atac asupra sistemului în producție) - aceasta a fost o evaluare de cod, nu un pentest
  • Infrastructura de găzduire, rețeaua, configurarea serverului
  • Securitatea fizică și procesele organizaționale

Precizare legală
Aceasta este o evaluare tehnică comercială. Nu este un „audit de securitate cibernetică" în sensul OUG 155/2024, iar COXSWAIN nu este auditor atestat DNSC. Este o fotografie a stării codului la data evaluării; nu garantează absența altor probleme și nu constituie certificare de conformitate.

SECȚIUNEA 3

Constatări

Sumar

IDGravitateConstatareReferință
COX-01CriticAutorizare lipsă la nivel de obiect (IDOR)API1:2023
COX-02CriticSecrete de producție în cod și în istoricul gitASVS 2.10
COX-03RidicatSQL injection prin concatenare de stringASVS 5.3.4
COX-04RidicatFără limitare a ratei la autentificareASVS 2.2.1
COX-05RidicatDependențe cu vulnerabilități cunoscuteASVS 14.2
COX-06MediuParole hash-uite cu algoritm rapid (SHA-256)ASVS 2.4.1
COX-07MediuJWT fără expirare, secret slab hardcodatASVS 3.5
COX-08MediuValidare doar în frontendASVS 5.1.3
COX-09MediuErori înghițite, fără logareASVS 7.1
COX-10ScăzutStack trace expus în producțieASVS 7.4.1

Detaliat

COX-01CriticAutorizare lipsă la nivel de obiect (IDOR)
Locație
src/routes/orders.js:42
Descriere
Endpoint-ul GET /api/orders/:id returnează comanda după ID fără a verifica dacă aparține utilizatorului autentificat. Autentificarea există (requireAuth), dar autorizarea lipsește.
Impact
Orice utilizator logat poate citi comenzile oricui schimbând ID-ul din adresă (/api/orders/1041/1042): nume, adrese, produse, sume. Expunere de date personale - încălcare GDPR, raportabilă în 72h.
Cod actual - problema
router.get('/api/orders/:id', requireAuth, async (req, res) => {
  // autentificat, dar NU verifică al cui e comanda
  const order = await db.query(
    'SELECT * FROM orders WHERE id = $1', [req.params.id]);
  res.json(order.rows[0]);
});
Remediere
router.get('/api/orders/:id', requireAuth, async (req, res) => {
  const order = await db.query(
    'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
    [req.params.id, req.user.id]);
  if (!order.rows[0]) return res.status(404).json({ error: 'Not found' });
  res.json(order.rows[0]);
});

De verificat și în alte părți: același tipar apare probabil la /api/bookings/:id și /api/invoices/:id. Recomand un middleware de verificare a proprietății, aplicat consistent.

COX-02CriticSecrete de producție în cod și în istoricul git
Locație
.env (urcat în repo) · istoric git, commit a3f9c2e
Descriere
gitleaks a detectat 3 secrete cu risc ridicat urcate în repository: parola bazei de date, JWT_SECRET, și cheia Stripe sk_live_… (producție, nu test).
Impact
Oricine are acces la repo (angajați, colaboratori, un leak) deține cheile de producție. Cheia Stripe live permite operațiuni financiare reale. Secretele sunt compromise din momentul urcării, indiferent ce se întâmplă de acum.

Ordinea de remediere contează

  1. Rotește imediat toate 3 secretele (Stripe dashboard, parolă DB, JWT). Sunt deja compromise - ștergerea din cod nu le face sigure.
  2. Adaugă .env în .gitignore.
  3. Curăță istoricul git (git filter-repo sau BFG). Ștergerea din ultimul commit NU e suficientă - secretele rămân în istoric.
  4. Mută secretele într-un secret manager (variabile de mediu la nivel de platformă, Vault, etc.).
COX-03RidicatSQL injection prin concatenare de string
Locație
src/routes/search.js:19
Descriere
Termenul de căutare al utilizatorului e concatenat direct în interogarea SQL, fără parametrizare.
Impact
?term=' OR 1=1-- returnează toate produsele; injecții mai complexe pot citi alte tabele (utilizatori, comenzi) sau, în funcție de permisiunile bazei, modifica date.
Cod actual - problema
const q = `SELECT * FROM products
           WHERE name LIKE '%${req.query.term}%'`;  // injectabil
const results = await db.query(q);
Remediere
const results = await db.query(
  'SELECT * FROM products WHERE name LIKE $1',
  [`%${req.query.term}%`]);  // parametrizat
COX-04RidicatFără limitare a ratei la autentificare
Locație
src/routes/auth.js:28
Descriere
POST /api/login nu are nicio limitare a numărului de încercări. Permite brute-force și credential stuffing nelimitat.
Impact
Un atacator poate încerca mii de parole pe minut. Combinat cu hash-urile slabe din COX-06, conturile cad rapid.
Remediere
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, max: 5,   // 5 încercări / 15 min / IP
  message: 'Prea multe încercări. Reîncearcă în 15 minute.'
});
router.post('/api/login', loginLimiter, loginHandler);

Ideal, adaugă lockout progresiv pe cont și autentificare în doi factori (MFA) pentru conturile administrative.

COX-05RidicatDependențe cu vulnerabilități cunoscute
Locație
package.json
Descriere
Trivy a găsit dependențe cu CVE-uri publice:
PachetVersiuneCVEProblemă
jsonwebtoken8.3.0CVE-2022-23529Execuție de cod la verificarea token-ului
lodash4.17.11CVE-2019-10744Prototype pollution
axios0.21.0CVE-2020-28168SSRF

Impact: variabil, dar jsonwebtoken vechi combinat cu JWT-ul slab din COX-07 e o combinație periculoasă. Remediere: npm audit fix, actualizare la versiunile cu patch, și integrarea trivy sau npm audit în CI ca poartă de blocare la fiecare build.

COX-06MediuParole hash-uite cu algoritm rapid (SHA-256)
Locație
src/services/user.js:54
Descriere
Parolele sunt hash-uite cu SHA-256, un algoritm proiectat să fie rapid. Un GPU testează miliarde de hash-uri pe secundă.
Impact
La un eventual leak al bazei de date, parolele utilizatorilor cad în ore. Fără „salt" per utilizator, atacul e și mai eficient.
Cod actual
const hash = crypto.createHash('sha256')
  .update(password).digest('hex');  // rapid = slab pentru parole
Remediere
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);  // lent, cu salt inclus
// verificare: await bcrypt.compare(password, hash);

Migrare: rehash la următorul login reușit al fiecărui utilizator, transparent.

COX-07MediuJWT fără expirare, secret slab hardcodat
Locație
src/services/auth.js:12
Descriere
Token-urile JWT sunt semnate cu un secret trivial hardcodat și fără termen de expirare.
Impact
Un token furat e valabil la infinit. Secretul 'secret123' e brute-force-abil → un atacator poate forja token-uri valide pentru orice utilizator, inclusiv admin.
Cod actual
jwt.sign({ userId: user.id }, 'secret123');  // secret slab, fără expirare
Remediere
jwt.sign(
  { userId: user.id },
  process.env.JWT_SECRET,          // ≥256 biți, din secret manager
  { expiresIn: '15m' });           // + refresh tokens pentru sesiuni lungi
COX-08MediuValidare doar în frontend
Locație
src/routes/booking.js:15
Descriere
Endpoint-ul de rezervare validează cantitatea și data doar în React. Serverul acceptă orice trimite clientul direct către API.
Impact
Se pot trimite cantități negative, date în trecut, câmpuri lipsă. Poate produce date inconsistente sau abuzuri de logică de business (ex. cantitate negativă → credit).
Remediere - validare pe server
const { z } = require('zod');
const schema = z.object({
  quantity: z.number().int().positive(),
  date: z.string().datetime().refine(d => new Date(d) > new Date())
});
const parsed = schema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error);

Validarea din frontend e pentru comoditatea utilizatorului. Granița de securitate e serverul, mereu.

COX-09MediuErori înghițite, fără logare
Locație
src/routes/payment.js:61 și încă 7 locuri
Descriere
Blocuri catch goale, care înghit erorile fără logare sau alertă.
Impact
Eșecuri silențioase. Dacă procesarea plății pică, nimeni nu află până nu reclamă clientul. Debugging aproape imposibil, incidente nedetectate - și un obstacol direct la cerințele de detecție și raportare din CRA și NIS2.
Cod actual
try {
  await processPayment(order);
} catch (e) {}  // eroarea dispare în tăcere
Remediere
try {
  await processPayment(order);
} catch (e) {
  logger.error({ err: e, orderId: order.id }, 'plata a eșuat');
  await alertOnCriticalFailure(e);      // monitorizare + alertă
  throw e;                              // nu ascunde eșecul
}
COX-10ScăzutStack trace expus în producție
Locație
src/middleware/error.js:9
Descriere
Handler-ul global de erori trimite err.stack în răspunsul HTTP, inclusiv în producție.
Impact
Divulgă structura internă, căile de fișiere și versiunile - ajută un atacator în faza de recunoaștere. Risc scăzut singur, dar util combinat cu alte probleme.
Remediere
const isProd = process.env.NODE_ENV === 'production';
res.status(500).json({
  error: isProd ? 'Eroare internă' : err.message  // stack doar în log
});
logger.error({ err }, 'eroare neprinsă');
SECȚIUNEA 4

Plan de remediere

Ordonat pe risc, nu pe efort. Începeți de sus.

Acum - în 24 de ore

#AcțiuneEfort
COX-02Rotește cele 3 secrete de producție (Stripe, DB, JWT). Sunt compromise. Restul curățării poate urma, dar rotația e acum.1-2 h
COX-01Adaugă verificarea proprietății pe /orders/:id și endpoint-urile similare.2-4 h
COX-03Parametrizează interogarea de căutare (și verifică restul codebase-ului pentru același tipar).1-2 h

Sprintul acesta

#AcțiuneEfort
COX-04Rate limiting la login + lockout progresiv2 h
COX-05Actualizare dependențe vulnerabile + npm audit în CI3-4 h
COX-07JWT: secret puternic din env + expirare + refresh tokens3-4 h
COX-06Migrare la bcrypt cu rehash la login3 h

Următorul sprint

#AcțiuneEfort
COX-08Validare pe server pe toate endpoint-urile care primesc input4-6 h
COX-09Logare structurată + monitorizare + alerte pe eșecuri critice4 h
COX-10Ascunderea stack trace-ului în producție30 min

Reverificare inclusă
După ce implementați remedierile, revin cu o reverificare a celor 10 constatări și confirm în scris ce e închis. Reverificarea e inclusă în această colaborare.

SECȚIUNEA 5

Anexă - trierea

Uneltele automate au ridicat 47 de alerte. Am verificat manual fiecare. Iată bilanțul, și un exemplu de fals pozitiv, ca să vedeți ce înseamnă „triere".

SursăAlerte bruteRealeZgomot
Semgrep CE31625
gitleaks413
Trivy1239
Total471034

Exemplu de fals pozitiv

-Fals pozitiv„Hardcoded password" care nu e o parolă
Locație
src/config/constants.js:4
Ce a semnalat
Semgrep
„Hardcoded password detected"
Codul semnalat
const DEFAULT_PASSWORD_FIELD = 'password';  // numele câmpului, nu o parolă

De ce NU e o problemă: valoarea 'password' e numele unui câmp de formular, nu o parolă reală. Scannerul se declanșează pe cuvântul „password" în context de string. 34 din 47 de alerte au fost de acest tip. Un raport care le-ar fi inclus pe toate ar fi fost inutilizabil. Trierea e munca - și e diferența dintre un raport de inginer și un export de scanner.

COXSWAIN · S.C. COXSWAIN S.R.L. · CUI RO51383300 · București · coxswain.ro

Document model cu date fictive, folosit ca șablon. Un raport real e confidențial și specific unui singur client. Această evaluare reflectă starea codului la data indicată; nu garantează absența altor probleme și nu constituie audit reglementat sau certificare de conformitate.