Amb la identitat verificada (07-01) i el canal xifrat (07-02), un atacant ja no pot llegir el token de l'Ana ni fer-se passar per Comandes. Però pot continuar sent un usuari legítim que envia una petició malintencionada: un id amb caràcters estranys, un JSON de 50 MB, un filtre de MongoDB disfressat de cadena, o simplement GET /v1/comandes/com-88214 per veure si cola. Aquesta lliçó tracta del que cada servei fa amb el que rep i amb el que desa: els principis que ordenen tota la resta, l'OWASP API Security Top 10 recorregut amb exemples de TechCorp, validació i sanejament d'entrada amb zod, les injeccions (SQL, NoSQL, ordres, logs), capçaleres amb helmet, dependències, secrets al codi, protecció de dades personals (RGPD a nivell pràctic), auditoria, proves de seguretat i resposta davant de vulnerabilitats. Acaba amb la llista de comprovació que s'incorpora a la plantilla techcorp/plantilla-servei-node. Autenticació (07-01), TLS (07-02) i contenidors/Kubernetes (07-04) només s'enllacen.

Avís. Les mesures d'aquesta lliçó són les habituals en un equip de desenvolupament, no un programa de seguretat complet. L'aplicació real del RGPD, de PCI DSS i de qualsevol política de retenció o auditoria s'ha de revisar amb un professional de seguretat i amb el responsable de compliment normatiu/protecció de dades de l'organització.

Contingut

  1. Principis que ordenen la resta
  2. OWASP API Security Top 10 (2023) amb exemples de TechCorp
  3. Validació i sanejament d'entrada amb zod
  4. Injecció: SQL, NoSQL, ordres i logs
  5. Capçaleres de seguretat amb helmet
  6. Gestió de dependències
  7. Secrets al codi: mai, i com assegurar-ho
  8. Protecció de dades personals: RGPD a la pràctica
  9. Auditoria d'accions sensibles
  10. Proves de seguretat: SAST, DAST, revisió i pentest
  11. Resposta davant de vulnerabilitats
  12. Llista de comprovació de seguretat per servei

  1. Principis que ordenen la resta

Sis principis que es repeteixen a cada decisió d'aquest mòdul:

Principi Significat Ja aplicat a TechCorp
Mínim privilegi Cada component només pot fer el que necessita Usuaris svc_* per esquema (02-04), permisos de RabbitMQ per cua (07-02), scopes per client (07-01)
Defensa en profunditat Diverses capes independents; cap no és l'única JWT verificat al gateway i al servei; TLS i signatura HMAC; validació i consultes parametritzades
Fallar segur Davant del dubte, denegar; davant de l'error, no revelar AuthorizationPolicy amb ALLOW explícit; 500 ERROR_INTERN sense traça (04-02); config invàlida → no arrenca (04-03)
Superfície mínima Menys portes, menys risc Inventari/Pagaments/Notificacions no exposats (03-04); x-powered-by desactivat; imatge sense npm de desenvolupament (05-01)
Seguretat per disseny Es decideix en dissenyar, no s'apedaça al final Propietari del recurs al cas d'ús; esdeveniments sense dades innecessàries (02-05)
Shift left Comprovar tan aviat com es pugui en el cicle Lint, proves, npm audit, Trivy i gitleaks a CI (05-03) abans de desplegar

  1. OWASP API Security Top 10 (2023) amb exemples de TechCorp

L'OWASP API Security Top 10 és la llista de referència de riscos en APIs. Recórrer-la amb casos propis és la millor manera d'interioritzar-la:

# Risc Com es veuria a TechCorp Remei i on
API1 BOLA (Broken Object Level Authorization) L'Ana demana GET /v1/comandes/com-88214 (d'un altre client) i l'obté Comprovació de propietari a cada ruta amb {id} (07-01): comanda aliena → 404
API2 Autenticació trencada Acceptar alg: none, tokens d'hores, password grant, JWKS sense verificar aud jwtVerify amb algorithms, issuer, audience; tokens de 5 min (07-01)
API3 Exposició excessiva de dades / propietat a nivell d'objecte GET /v1/comandes/{id} retorna l'email i el keycloakSub de la rèplica clients_ref; PATCH que accepta estat o total des del client DTOs explícits (aDto/aRepresentacio de 04-02/04-04): es trien els camps que surten; esquemes zod amb .strict() que rebutgen camps no previstos
API4 Consum de recursos sense límit Un client demana ?limit=100000, envia cossos de 50 MB o fa 10.000 POST /v1/comandes per minut limit màx. 100 (03-01/04-02), express.json({ limit: '100kb' }), rate limit 300/min al gateway (03-04), timeout i bulkhead (06-03)
API5 Autorització trencada a nivell de funció Un client crida GET /v1/comandes?estat=PENDENT (llistat d'operador) o POST /v1/productes requereixRol('operador') a rutes d'operació (07-01); rutes d'administració no exposades pel gateway públic
API6 Accés sense restricció a fluxos de negoci sensibles Un bot crea milers de comandes d'un producte en oferta i esgota l'estoc Rate limit per usuari (no només per IP), CAPTCHA al web per a pics, límits de negoci (màx. N unitats per comanda/client)
API7 SSRF Un webhook configurable per l'usuari o una "URL d'imatge" que Catàleg descarrega apunta a http://10.0.0.5:15672 (RabbitMQ) o a http://169.254.169.254 (metadades del núvol) No acceptar URLs arbitràries de l'usuari; si és imprescindible, llista blanca de dominis, resoldre DNS i rebutjar IPs privades, sense seguir redireccions; egress restringit (07-04)
API8 Mala configuració CORS amb *, x-powered-by, traces d'error al client, NODE_ENV diferent de production CORS amb orígens concrets (03-04), helmet (apartat 5), middlewareErrors que amaga detalls (04-02)
API9 Inventari d'APIs deficient Una ruta /v1/comandes-legacy oblidada sense autenticació; una versió antiga encara desplegada OpenAPI com a font de veritat (03-06), contractes Pact (04-05), el gateway com a únic punt d'entrada, retirar versions amb data
API10 Consum insegur d'APIs de tercers Confiar cegament en la resposta de la passarel·la de pagament o d'un proveïdor d'adreces Validar també el que arriba de tercers amb zod (l'ACL traductorProducte de 04-04 fa exactament això), timeouts, HMAC als webhooks (07-02)

Els remeis ja existeixen majoritàriament al codi de TechCorp; aquesta lliçó els reuneix i afegeix els que falten.

  1. Validació i sanejament d'entrada amb zod

Regla: tot el que entra per la xarxa es valida contra un esquema abans de tocar-ho, tant el que ve de l'usuari com el que ve d'un altre servei o d'un esdeveniment. A 04-02 i 04-04 ja es fa amb zod; aquí s'endureix:

// servei-comandes/src/rutes/esquemes.js
const { z } = require('zod');

// Identificadors amb patró tancat: res de "com-88213' OR 1=1" ni de rutes amb "../"
const ComandaId  = z.string().regex(/^com-[0-9a-f]{8}$/, 'comandaId no vàlid');
const ClientId   = z.string().regex(/^c-[0-9]{1,10}$/);
const ProducteId = z.string().regex(/^p-[0-9]{1,10}$/);
const PAISOS = ['ES', 'PT', 'FR'];                                         // llista blanca, no "qualsevol ISO"

const Adreca = z.object({
  carrer: z.string().trim().min(1).max(120),
  codiPostal: z.string().regex(/^[0-9]{5}$/),
  ciutat: z.string().trim().min(1).max(80),
  pais: z.enum(PAISOS)
}).strict();                                                               // camps desconeguts → 400 (no s'ignoren en silenci)

const CrearComanda = z.object({
  clientId: ClientId,
  linies: z.array(z.object({ producteId: ProducteId, quantitat: z.number().int().min(1).max(50) }).strict()).min(1).max(30),
  adrecaEnviament: Adreca
}).strict();

const LlistarComandes = z.object({
  estat: z.enum(['PENDENT', 'CONFIRMADA', 'CANCELLADA']).optional(),
  clientId: ClientId.optional(),
  limit: z.coerce.number().int().min(1).max(100).default(20),
  cursor: z.string().max(200).optional()
}).strict();

module.exports = { ComandaId, CrearComanda, LlistarComandes };

I a les rutes, també els paràmetres de ruta, que molts obliden:

// servei-comandes/src/rutes/comandes.js (fragments)
encaminador.get('/v1/comandes/:id', requereixScope('comandes:llegir'), async (req, res, next) => {
  try {
    const id = ComandaId.parse(req.params.id);         // ZodError → 400 PETICIO_INVALIDA pel middlewareErrors (04-02)
    // ... propietari (07-01), repositori.obtenir(id), ETag ...
  } catch (err) { next(err); }
});
encaminador.post('/v1/comandes', requereixScope('comandes:crear'), async (req, res, next) => {
  try {
    const peticio = CrearComanda.parse(req.body);      // a partir d'aquí, "peticio" és de confiança en forma; el negoci decideix la resta
    // ...
  } catch (err) { next(err); }
});

I a app.js els límits globals que ja portava la plantilla, ara justificats: express.json({ limit: '100kb', strict: true }) (un cos més gran → 413; strict rebutja JSON que no sigui objecte o array), i al gateway proxy-body-size: 2m a l'Ingress (05-02) com a sostre absolut. Sanejament enfront de validació: TechCorp prefereix rebutjar (400) a "netejar" silenciosament; l'única normalització acceptada és trim() i la de majúscules en codis (pais.toUpperCase()), i sempre a l'esquema, mai dispersa.

  1. Injecció: SQL, NoSQL, ordres i logs

La injecció passa quan dades de l'usuari s'interpreten com a codi o estructura. Quatre variants que afecten TechCorp:

SQL (PostgreSQL amb pg). Mai concatenar; sempre paràmetres posicionals, com ja fa ComandaRepositori a 04-04:

// MALAMENT: si estat = "PENDENT' OR '1'='1", ho retorna tot; si conté "; DROP TABLE comandes; --", pitjor
await bd.query(`SELECT * FROM comandes.comandes WHERE estat = '${estat}'`);
// BÉ: el valor viatja a part de la sentència; el motor mai no l'interpreta com a SQL
await bd.query('SELECT * FROM comandes.comandes WHERE estat = $1 AND client_id = $2 ORDER BY creat_en DESC LIMIT $3', [estat, clientId, limit]);
// Els identificadors (nom de columna per a ORDER BY) NO es poden parametritzar: llista blanca
const COLUMNES_ORDRE = { creatEn: 'creat_en', total: 'total_centims' };
const columna = COLUMNES_ORDRE[ordre] ?? 'creat_en';

NoSQL (MongoDB a Catàleg). El risc és diferent: no és text, sinó objectes. Si una ruta fa colleccio.find({ sku: req.query.sku }) i el client envia ?sku[$ne]=x, Express (amb qs) construeix { sku: { $ne: 'x' } } i la consulta retorna tots els productes; amb $where o $regex pot ser pitjor. Regla: mai passar objectes de l'usuari al filtre; validar amb zod que cada valor és una cadena (o un número) i construir el filtre al repositori amb les claus fixes:

// servei-cataleg/src/rutes/productes.js
const Consulta = z.object({ ids: z.string().optional(), categoria: z.string().regex(/^[a-z-]{1,40}$/).optional(), limit: z.coerce.number().int().min(1).max(100).default(20) }).strict();
const q = Consulta.parse(req.query);                   // ?categoria[$ne]=x → falla: no és string
// repositori: const filtre = {}; if (q.categoria) filtre.categoria = q.categoria;  // la clau la posa el codi, el valor és un string validat

A més, express.json no interpreta operadors, però un cos { "filtre": { "$where": "..." } } sí que arribaria com a objecte: mateix remei (esquema .strict(), res de filtre lliure).

Ordres del sistema. Gairebé mai no fan falta en un servei; si n'hi ha (generar un PDF, convertir una imatge), execFile('convert', [ruta]) amb arguments en array, mai exec(\convert ${nom}`)`, i el nom de fitxer validat amb patró. Encara millor: una llibreria nativa.

Logs. El log injection: un usuari posa a carrer el text "Gran Vía 12\n{\"nivell\":\"info\",\"missatge\":\"comanda pagada\"}" i, si el log fos text, hi apareixeria una línia falsa. Amb pino (06-01) el valor va serialitzat dins d'un JSON (els salts de línia s'escapen com a \n), així que l'estructura no es trenca; la regla és no construir missatges concatenant entrada de l'usuari (log.info(\comanda de ${nom}`)) sinó passar-la com a camp (log.info({ clientId }, 'comanda creada')`), i recordar que les dades personals no van al log en cap cas (apartat 8).

  1. Capçaleres de seguretat amb helmet

helmet és un conjunt de middlewares d'Express que fixa capçaleres HTTP defensives. Al gateway (el que veu el navegador) i a cada servei (defensa en profunditat; i alguns, com el BFF, serveixen HTML):

// gateway/servidor.js i plantilla de servei (app.js), després de middlewareRequestId
const helmet = require('helmet');
app.use(helmet({
  contentSecurityPolicy: false,          // l'API no serveix HTML; la CSP la defineix la SPA/BFF que sí que el serveix
  strictTransportSecurity: { maxAge: 31_536_000, includeSubDomains: true },   // HSTS (07-02): coherent amb l'Ingress
  crossOriginResourcePolicy: { policy: 'same-site' }
}));
Capçalera Efecte Qui la necessita
Strict-Transport-Security Només HTTPS durant un any Gateway (i l'Ingress, 07-02)
X-Content-Type-Options: nosniff El navegador no "endevina" tipus MIME (una resposta JSON no s'executa com a script) Tots
X-Frame-Options: DENY / CSP frame-ancestors Evita el clickjacking BFF/web; innòcua en APIs
Content-Security-Policy Quins orígens d'script/estil es permeten botiga-web i bff-mobil si serveixen HTML; desactivada a l'API
Referrer-Policy, X-DNS-Prefetch-Control, Cross-Origin-* Fuites d'informació en navegar Web
(elimina) X-Powered-By No anunciar Express Tots (app.disable('x-powered-by') ja a 04-02)

helmet no substitueix CORS (03-04): CORS diu des de quin origen pot cridar el navegador; helmet diu com ha de tractar el navegador la resposta.

  1. Gestió de dependències

Un servei-comandes típic arrossega 300 paquets transitius; la majoria de les vulnerabilitats reals entren per aquí. La política de TechCorp:

Pràctica Com Quan
package-lock.json versionat i npm ci El lockfile fixa versions exactes; npm ci falla si no coincideix amb package.json (05-01, 05-03) Sempre
npm audit --omit=dev --audit-level=high Falla el pipeline si hi ha vulnerabilitat alta/crítica amb pedaç en dependències de producció A ci.yml, etapa de lint
Dependabot o Renovate Pull requests automàtiques d'actualització, agrupades per setmana; els pedaços de seguretat, immediats Configuració a .github/dependabot.yml
Política d'actualització Menors i pedaços: s'accepten si passen CI i contracte; majors: revisió de l'equip Regla del Luis (05-03)
Dependències mínimes Abans d'afegir un paquet: ho porta Node? (fetch, crypto, AbortSignal), està mantingut?, quants transitius arrossega? Revisió de PR
Fixar la imatge base i les accions de CI node:20-alpine amb digest (07-04), actions/checkout@v4 per versió Plantilla
SBOM Llista de materials de cada imatge (Syft, 07-04) per saber en minuts si "fem servir la llibreria X" Esment; es genera a CI

I npm audit a CI no és infal·lible: revisa l'avís, no el silenciïs amb --force; si no hi ha pedaç, avalua el risc (la funció vulnerable es fa servir?) i documenta l'excepció amb data de revisió.

  1. Secrets al codi: mai, i com assegurar-ho

La regla existeix des de 04-03 (els secrets arriben per l'entorn o per fitxers _FILE, mai al repositori ni a la imatge); aquí es fa verificable:

# .github/workflows/servei-node-ci.yml (fragment, el workflow reutilitzable de 05-03)
      - name: Cercar secrets a l'historial
        uses: gitleaks/gitleaks-action@v2          # detecta claus AWS, tokens, contrasenyes en URLs, claus privades...
        env: { GITLEAKS_LICENSE: "" }               # gratuït per a repos d'organització amb llicència; alternativa: git-secrets o trufflehog

I en local, un hook de pre-commit amb gitleaks protect --staged. Si un secret arriba al repositori, l'única resposta correcta és rotar-lo (l'historial de git és públic per sempre per a qui l'hagi clonat): la mecànica de rotació —Secrets, External Secrets, reinicis— és de 07-04. Afegeix al .gitignore de la plantilla .env, *.pem, *.key; i recorda que un secret en un console.log, en una traça d'error o a la URL d'una petició (?token=) també és una fuita: per això redact (06-01) i configPerALog() (04-03).

  1. Protecció de dades personals: RGPD a la pràctica

TechCorp tracta dades personals de l'Ana (nom, correu, adreça) i dades de pagament. Sense pretendre substituir el responsable de protecció de dades, les decisions tècniques que un equip ha de prendre:

  • Minimització. Ha de portar comanda.creada el client.email i el nom (02-02)? Es necessiten perquè Notificacions enviï el correu sense cridar Clients (autonomia, 03-02). Alternatives: (a) l'esdeveniment porta les dades i tots els consumidors les reben (Inventari no les necessita); (b) l'esdeveniment porta només clientId i Notificacions consulta GET /v1/clients/{id} amb el seu token de servei. Decisió de TechCorp: (a) per autonomia i perquè Notificacions és l'únic subscriptor que les fa servir avui, amb tres condicions: les dades personals només van a comanda.creada (no a la resta d'esdeveniments de la saga), RabbitMQ va xifrat (07-02) i les cues i la DLQ tenen retenció limitada (missatges a .dlq de més de 7 dies es purguen després de revisar-se: no són un arxiu); i es revisarà cap a (b) si apareix un segon consumidor que no les necessita.
  • Pseudonimització. Fora de Clients, els serveis desen clientId, no el nom; els logs i mètriques porten només identificadors (06-01); les traces de Jaeger no porten cossos.
  • Xifratge en repòs. A les bases de dades gestionades està actiu per defecte (discos xifrats); per a columnes especialment sensibles es pot xifrar a nivell d'aplicació amb una clau del gestor de secrets, a costa de no poder indexar-les. Còpies de seguretat xifrades i amb la mateixa retenció.
  • Dret de supressió. El flux de 02-04: servei-clients anonimitza i publica client.eliminat; Comandes esborra clients_ref i anonimitza adreces de comandes antigues que la normativa fiscal obligui a conservar; Notificacions no desa res. És un cas d'ús més, amb prova automatitzada.
  • Dades de targeta: MAI a TechCorp. El navegador envia la targeta directament a la passarel·la (formulari o SDK del proveïdor); TechCorp rep un token (tok_…) que servei-pagaments fa servir per cobrar. Així l'abast de PCI DSS es redueix al mínim (SAQ A). Ni el número, ni el CVV, ni tan sols "els quatre últims dígits" llevat que la passarel·la els retorni ja emmascarats. *.targeta a redact és una xarxa de seguretat per si algú s'equivoca, no un permís.
  • Registre de tractaments i retenció: cada servei documenta quines dades personals desa, per a què i durant quant de temps (taula al seu README), i un CronJob aplica la retenció (p. ex. anonimitzar adreces de comandes de més de 5 anys).

  1. Auditoria d'accions sensibles

Els logs de 06-01 expliquen què ha passat tècnicament; l'auditoria explica qui ha fet què sobre què, i s'ha de poder demostrar mesos després. Accions auditables a TechCorp: cancel·lar una comanda (qui, motiu), canviar un preu a Catàleg, canviar l'estat d'un pagament manualment, reprocessar la DLQ (scripts/reprocessarDlq.js, 06-03), accedir a dades d'un client per part d'un operador, canviar rols a Keycloak. Requisits: immutable (només inserció, sense UPDATE/DELETE per a l'usuari svc_*), separada dels logs operatius (retenció diferent: anys enfront de setmanes), i correlacionable amb X-Request-Id i el sub del token (X-Usuari-Id, 07-01).

// @techcorp/comu-http/src/auditoria.js — un registre per acció sensible
function crearAuditoria({ bd, servei }) {
  return {
    async registrar(req, { accio, recurs, recursId, detall = {} }) {
      // taula <esquema>.auditoria(id, ocorregut_en, servei, accio, recurs, recurs_id, usuari_id, rols, request_id, detall jsonb)
      // L'usuari svc_* només té INSERT sobre ella; la lectura, un rol a part (auditor)
      await bd.query(
        'INSERT INTO auditoria (ocorregut_en, servei, accio, recurs, recurs_id, usuari_id, rols, request_id, detall) VALUES (now(), $1, $2, $3, $4, $5, $6, $7, $8)',
        [servei, accio, recurs, recursId, req.usuari?.sub ?? 'sistema', (req.usuari?.roles ?? []).join(','), req.id, JSON.stringify(detall)]
      );
    }
  };
}
// Ús a la cancel·lació (03-01): await auditoria.registrar(req, { accio: 'COMANDA_CANCELLADA', recurs: 'comanda', recursId: comanda.comandaId, detall: { motiu } });

Amb volum alt, la taula se substitueix per esdeveniments auditoria.* cap a un magatzem de només escriptura (o el log estructurat amb tipus: 'auditoria' enviat a un tenant de Loki amb retenció llarga i sense esborrat); l'important és la separació i la immutabilitat, no la tecnologia. detall no conté dades personals: identificadors i valors de negoci.

  1. Proves de seguretat: SAST, DAST, revisió i pentest

Tipus Què fa Eina a TechCorp Quan
SAST (anàlisi estàtica) Cerca patrons perillosos al codi sense executar-lo eslint-plugin-security a la configuració de la plantilla; Semgrep amb les regles p/nodejs i p/owasp-top-ten a CI Cada PR
Anàlisi de dependències Vulnerabilitats conegudes npm audit, Dependabot (apartat 6) Cada PR i diari
Escaneig d'imatges CVEs a la imatge Trivy (07-04) Cada build
DAST (dinàmic) Ataca l'API desplegada OWASP ZAP en mode API (zap-api-scan.py amb l'OpenAPI de 03-06) contra staging, després de l'E2E de 05-03 Cada desplegament a staging (informe), setmanal (bloquejant en alt)
Revisió de codi Ulls humans amb llista de comprovació Llista de l'apartat 12 a la plantilla de PR; revisor d'un altre equip per a rutes amb {id} o dades personals Cada PR
Pentest Un professional extern intenta trencar-ho Empresa externa; abast: gateway, botiga-web, Keycloak, webhooks Anual i abans de l'auditoria de Pagaments
# .github/workflows/servei-node-ci.yml (fragment SAST)
      - name: Semgrep
        uses: semgrep/semgrep-action@v1
        with: { config: "p/nodejs p/owasp-top-ten p/secrets" }

I les proves de seguretat d'aplicació són proves normals: el test/comandes.auth.test.js de 07-01 (401/403/404) i casos com "un id que no compleix el patró retorna 400", "un cos amb un camp desconegut retorna 400", "?categoria[$ne]=x retorna 400" viuen a Jest al costat de la resta i protegeixen contra regressions.

  1. Resposta davant de vulnerabilitats

Tard o d'hora algú trobarà alguna cosa. El que TechCorp deixa preparat:

  1. Canal de report: [email protected] i un fitxer /.well-known/security.txt a shop.techcorp.example (RFC 9116) amb contacte i política; agraïment públic (sense recompensa formal a la fase 1).
  2. Triatge: gravetat amb CVSS; crítica/alta → incident de seguretat amb el procés de 06-05 (canal, responsable, cronologia), encara que no hi hagi caiguda de servei.
  3. Apedaçament: branca des de main, prova que reprodueix la fallada, desplegament pel pipeline normal (canary si escau, 05-04); dependència vulnerable → actualització o mitigació temporal (bloquejar la ruta al gateway).
  4. Comunicació: interna (tots els equips, perquè el mateix patró pot ser en un altre servei) i, si hi ha hagut accés a dades personals, notificació a l'autoritat de control en 72 hores i als afectats segons el RGPD: decisió del responsable de protecció de dades, no de l'equip tècnic.
  5. Postmortem sense culpa (06-05) amb accions: nova regla de Semgrep, nou cas a la llista de comprovació, prova de regressió.

  1. Llista de comprovació de seguretat per servei

Aquesta taula s'afegeix a techcorp/plantilla-servei-node/SEGURETAT.md i a la plantilla de pull request; cada servei la revisa en crear-se i a cada canvi rellevant:

Àrea Comprovació Referència
Autenticació autenticar() a /v1/*; sondes i /metrics sense token; algorithms, issuer, audience fixats 07-01
Autorització Tota ruta amb {id} comprova propietari o rol; requereixScope/requereixRol a rutes d'operació 07-01, API1/API5
Entrada Esquemes zod .strict() per a cos, query i paràmetres de ruta; ids amb patró; limit ≤ 100; express.json({ limit }) Apartat 3, API4
Sortida DTOs explícits; mai email/keycloakSub de tercers; errors sense traça Apartat 2 (API3), 04-02
Injecció Consultes parametritzades; filtres MongoDB amb claus fixes; sense exec amb entrada de l'usuari; log per camps Apartat 4
Capçaleres helmet, x-powered-by off, CORS amb orígens concrets Apartat 5
Dependències npm ci, npm audit a CI, Dependabot actiu, sense paquets sense mantenir Apartat 6
Secrets Cap al repo/imatge/log; gitleaks a CI; redact i configPerALog() cobreixen els nous Apartat 7, 07-04
Dades personals Taula de tractaments al README; retenció amb CronJob; reacciona a client.eliminat si desa dades de clients; res de targetes Apartat 8
Auditoria Accions sensibles registrades amb usuari_id i request_id; taula de només inserció Apartat 9
Proves Casos 400/401/403/404 a Jest; Semgrep i ZAP en verd o amb excepcions documentades Apartat 10
Canal TLS a BD i broker, usuari svc_* mínim 07-02
Plataforma securityContext, NetworkPolicy, ServiceAccount propi 07-04

Errors Comuns i Consells

  • Validar només el cos i deixar req.params.id i req.query sense esquema: la meitat de les injeccions i BOLA entren per aquí.
  • Esquemes permissius (z.object sense .strict(), z.string() sense max) que accepten qualsevol camp o cadenes de 10 MB: la validació ha de ser tan estricta com el contracte OpenAPI.
  • "Sanejar" en comptes de rebutjar: treure caràcters "perillosos" amb expressions regulars casolanes mai no cobreix tots els casos i canvia les dades de l'usuari d'amagat.
  • Confiar en la resposta de tercers perquè "és la passarel·la": API10 existeix perquè els tercers també s'equivoquen i també els ataquen.
  • Registrar el cos de la petició en info "per depurar" i descobrir setmanes després que Loki desa adreces i correus.
  • Silenciar npm audit amb --force o audit-level=critical perquè CI passi: cada excepció, documentada amb data.
  • Considerar l'auditoria "un log més" i esborrar-la als 30 dies amb la retenció de Loki.
  • Consell: cada regla d'aquesta lliçó que es pugui automatitzar (lint, Semgrep, gitleaks, ZAP, prova Jest) val més que la mateixa regla en un document; la llista de comprovació és per al que no es pot automatitzar.

Exercicis

Exercici 1. Un desenvolupador proposa afegir a servei-cataleg la ruta GET /v1/productes/cercar?filtre=<json> que rep un objecte de filtre de MongoDB "perquè el front pugui fer consultes flexibles". Explica el problema amb la numeració d'OWASP i proposa un disseny segur que cobreixi les necessitats reals (cercar per text, categoria i rang de preu).

Exercici 2. Escriu l'esquema zod per a POST /v1/comandes/{id}/cancellacio (03-01: motiu d'una llista permesa, comentari opcional) incloent-hi el paràmetre de ruta, i el registre d'auditoria corresponent amb crearAuditoria.

Exercici 3. La Marta pregunta si, per complir el RGPD, n'hi ha prou que Notificacions "esborri el correu quan arribi client.eliminat". Enumera quins altres llocs de la plataforma poden contenir el correu de l'Ana i quina mesura s'aplica a cadascun.

Solucions

Solució 1. És API8/API3 i una injecció NoSQL de manual: un objecte de filtre arbitrari permet { "$where": "..." }, { "preu": { "$gt": 0 } } sobre camps que no s'havien de filtrar, exposar camps interns (costProveidor), consultes sense índex que tomben Mongo (API4) i, amb $lookup en agregacions, llegir altres col·leccions. Disseny segur: paràmetres amb nom i esquema tancat, Cerca = z.object({ q: z.string().trim().min(2).max(60).optional(), categoria: z.string().regex(/^[a-z-]{1,40}$/).optional(), preuMin: z.coerce.number().min(0).optional(), preuMax: z.coerce.number().max(100000).optional(), limit: ...màx 100, cursor }).strict(); el repositori construeix el filtre amb claus fixes ({ nomNormalitzat: { $regex: escapar(q), $options: 'i' } } amb q escapat o, millor, un índex de text i $text: { $search: q }; preuCentims: { $gte, $lte }); només camps indexats; i la ruta retorna el DTO aDto de sempre. Si el front necessita alguna cosa més flexible, és un cas per a GraphQL al BFF (03-03), amb esquema tipat, no per passar objectes de Mongo.

Solució 2.

const MOTIUS = ['CLIENT_PENEDIT', 'ERROR_COMANDA', 'SENSE_ESTOC', 'FRAU'];
const Cancellacio = z.object({ motiu: z.enum(MOTIUS), comentari: z.string().trim().max(300).optional() }).strict();

encaminador.post('/v1/comandes/:id/cancellacio', requereixScope('comandes:crear'), async (req, res, next) => {
  try {
    const id = ComandaId.parse(req.params.id);
    const { motiu, comentari } = Cancellacio.parse(req.body);
    const comanda = await repositori.obtenir(id);
    // propietari/rol com a 07-01 (client: només la seva → si no, 404; operador: qualsevol; FRAU només operador)
    if (motiu === 'FRAU' && !req.usuari.roles.includes('operador')) throw new ErrorNegoci('PROHIBIT', 'Motiu reservat a operadors', 403);
    await cancellarComanda({ comanda, motiu, usuari: req.usuari });   // transició + outbox comanda.cancellada (04-04)
    await auditoria.registrar(req, { accio: 'COMANDA_CANCELLADA', recurs: 'comanda', recursId: id, detall: { motiu, teComentari: Boolean(comentari) } });
    res.status(202).set('Location', `/v1/comandes/${id}`).end();
  } catch (err) { next(err); }
});

El comentari no va a l'auditoria (text lliure de l'usuari, pot contenir dades personals): només si existeix. I l'auditoria es registra després de la transició, dins de la mateixa transacció si el repositori ho permet, per no auditar cancel·lacions que han fallat.

Solució 3. Llocs i mesures: (1) Base de dades de Clients: anonimitzar la fila (no esborrar, per integritat i obligacions fiscals), origen de l'esdeveniment. (2) clients_ref a Comandes (02-04): esborrar la fila i anonimitzar adreca_enviament de comandes antigues en rebre client.eliminat. (3) Missatges comanda.creada a cues, .reintent i .dlq de RabbitMQ: retenció curta i purga (apartat 8); si hi ha missatges a la DLQ del client, es processen o es descarten. (4) Logs a Loki: no hi hauria d'haver cap correu gràcies a redact i a la regla de no registrar-lo; si una auditoria en troba algun, és un incident (apartat 11) i cal esborrar-lo del magatzem. (5) Traces a Jaeger: sense cossos ni dades personals per disseny (06-02); comprovar els atributs d'span propis. (6) Còpies de seguretat: no s'editen; es documenta que la dada desapareix en expirar la retenció de la còpia (i s'anonimitza en restaurar). (7) Keycloak: esborrar o desactivar l'usuari (el sub), que és on viu el correu de login. (8) Proveïdor de correu de Notificacions (si conserva historial d'enviaments): configurar retenció mínima o sol·licitar l'esborrat per la seva API. (9) La taula d'auditoria: no ha de contenir correus (només usuari_id), i per disseny es conserva. La resposta a la Marta: "esborrar a Notificacions" és una de nou accions, i per això el dret de supressió és un cas d'ús amb llista i prova, no un DELETE.

Conclusió

Aquesta lliçó ha convertit la seguretat "del codi" en pràctiques concretes de TechCorp: sis principis (mínim privilegi, defensa en profunditat, fallar segur, superfície mínima, seguretat per disseny, shift left); l'OWASP API Security Top 10 llegit amb casos propis i remeis que ja existien (propietari del recurs, DTOs, limit ≤ 100, rate limit, CORS, OpenAPI i Pact) o que s'han afegit; esquemes zod .strict() amb ids per patró (^com-[0-9a-f]{8}$), llistes blanques i límits, també en paràmetres de ruta i query, amb express.json({ limit: '100kb' }); consultes parametritzades a pg, filtres de MongoDB amb claus fixes, execFile i logs per camps; helmet al gateway i als serveis; npm ci, npm audit, Dependabot i SBOM; gitleaks a CI i rotació davant de qualsevol fuita; RGPD pràctic (minimització amb la decisió de mantenir email a comanda.creada sota condicions, pseudonimització, xifratge en repòs, client.eliminat, retenció, i mai dades de targeta: tokenització de la passarel·la i abast PCI DSS mínim); auditoria immutable i separada amb crearAuditoria correlacionada per usuari_id i request_id; SAST amb ESLint/Semgrep, DAST amb ZAP contra staging, revisió amb llista i pentest anual; un procés de resposta amb security.txt i notificació RGPD; i la llista de comprovació que ara forma part de plantilla-servei-node. Queda l'última capa, la que sosté totes les altres: la imatge que executa el codi, el pod que la conté, els secrets que li arriben, la xarxa que l'envolta i els permisos del clúster. És el tema de la lliçó següent: seguretat en contenidors i Kubernetes.

Curs de Microserveis

Mòdul 1: Introducció als Microserveis

Mòdul 2: Disseny de Microserveis

Mòdul 3: Comunicació entre Microserveis

Mòdul 4: Implementació de Microserveis

Mòdul 5: Desplegament i Orquestració

Mòdul 6: Monitoratge i Manteniment

Mòdul 7: Seguretat en Microserveis

Mòdul 8: Casos d'Estudi i Exemples Pràctics

© Copyright 2026. Tots els drets reservats