Salta al contingut
Tornar a l'inici

00 / Arquitectura

Com està construït aquest web

Treballo en un SOC, així que aquest web és també una declaració d'intencions: si no fos capaç d'assegurar el meu propi lloc, poc tindria a aportar assegurant el de ningú. A sota hi ha les decisions que hi ha al darrere i el motiu de cadascuna, no una llista de tecnologies.

Resum del stack

Frontend
Astro 7 (SSG) · Tailwind CSS 4 · TypeScript
Backend
PHP 8 (PDO) · MySQL / MariaDB · sin framework
Infraestructura
CDMON · Apache · Cloudflare · TLS
Idiomes
Español · English · Català

01 / Superfície d'atac del frontend

El lloc es compila a HTML pla, de manera que la major part del risc clàssic de servidor desapareix. El que queda es tanca per capçalera.

  • CSP amb hash recalculats a cada compilació

    script-src porta el SHA-256 de cada script del lloc i no inclou ‘unsafe-inline’. Un <script> injectat no s'executa: no pot portar un hash que la política no conegui. Els hash es generen sols en compilar, no es mantenen a mà.

    astro.config.mjs

  • HSTS a un any, deliberadament sense preload

    includeSubDomains actiu. Es deixa fora de la llista de precàrrega expressament: entrar-hi és fàcil i sortir-ne triga mesos, i per a un portfolio aquest compromís no compensa.

    server/.htaccess

  • Aïllament del document

    X-Frame-Options DENY contra el clickjacking, Cross-Origin-Opener-Policy i Cross-Origin-Resource-Policy en same-origin, i Permissions-Policy que apaga geolocalització, micròfon i càmera encara que res no les demani.

    server/.htaccess

  • Res de tercers

    Sense CDN, sense Google Fonts, sense etiquetes d'analítica externa. Les tipografies van autoallotjades amb subset llatí. Cada domini que es treu és un tercer menys que pot caure, canviar el fitxer que serveix o registrar la IP del visitant.

  • Filtratge de projectes interactiu en client sense estat

    El filtratge de projectes per etiquetes s’executa íntegrament al navegador mitjançant manipulació de classes CSS. Evita desar estats en sessió o fer consultes HTTP addicionals per re-renderitzar, minimitzant la transferència de dades i la càrrega del servidor.

    src/components/Projects.astro

02 / Consola d'administració

La part amb accés a la base de dades i a les pujades. Aquí és on el detall importa de debò.

  • Consultes preparades reals, no emulades

    PDO amb ATTR_EMULATE_PREPARES a false. Amb l'emulació activada el driver interpola els valors al client abans d'enviar-los, i aquesta interpolació és justament on han viscut històricament els salts d'entrecometat en certs jocs de caràcters.

    server/db.php

  • Login que no revela si l’usuari existeix

    Contra un usuari inexistent es verifica igualment un hash fictici. Sense aquest pas, la resposta torna abans quan l'usuari no existeix i el temps permet enumerar comptes vàlids. Les contrasenyes van amb bcrypt i es rehashegen soles si puja el cost.

    server/admin/auth.php

  • CSRF en temps constant, amb el cas buit cobert

    El token es compara amb hash_equals. A més s'exigeix que el de la sessió existeixi i no estigui buit: sense aquesta condició, una petició sense galeta creava una sessió nova amb el token a cadena buida i la comparació de dues cadenes buides retornava cert, deixant passar la petició.

    server/admin/auth.php

  • Sessions endurides i amb caducitat doble

    Regeneració de l'identificador després del login (fixació de sessió), galetes HttpOnly, Secure i SameSite=Lax, use_strict_mode perquè ningú no imposi un identificador propi, i tancament tant per inactivitat com per durada màxima absoluta.

    server/admin/auth.php

  • Bloqueig de força bruta per IP

    Els intents fallits es compten en una finestra temporal i bloquegen la IP en superar el llindar. La IP es pren de REMOTE_ADDR llevat que es declari explícitament un proxy de confiança: fiar-se de X-Forwarded-For sense proxy al davant permet falsejar la IP i saltar-se el límit sencer.

    server/lib/http.php

  • Markdown segur al bàner superior

    El text del bàner superior s’edita en Markdown i es parseja al navegador. Abans de la conversió a HTML (enllaços, negretes, codi), la cadena s’escapa completament contra XSS, impedint que una potencial injecció en base de dades executi codi script.

    src/components/Announcement.astro

  • Controlador preventiu de migracions de base de dades

    El script d’inicialització verifica les taules i columnes necessàries en carregar la consola d’administració. Si falta alguna columna (per exemple, després de desplegar canvis de l’esquema), mostra una pantalla de manteniment amb instruccions de migració segures en comptes d’una fallada fatal de SQL.

    server/lib/bootstrap.php

03 / Contingut que no em pertany

Imatges pujades a la consola i missatges escrits per desconeguts: tot el que entra de fora es tracta com a hostil fins que es demostri el contrari.

  • Les imatges es validen per contingut, no per extensió

    Primer el tipus MIME real amb finfo, i després getimagesize(), que només reconeix imatges de debò. Un polyglot amb capçalera GIF falsificada passa el primer filtre però no el segon. Hi ha a mes un topall de dimensions perquè ningú no tombi el servidor amb una imatge de 30000 px de costat.

    server/lib/upload.php

  • La carpeta de pujades no executa codi

    Defensa en profunditat: encara que la validació fallés i s'hi colés un .php, el .htaccess d'aquella carpeta retira els handlers i apaga el motor, de manera que es serviria com a text pla. Els fitxers es desen amb nom aleatori, mai amb el que envia el client.

    server/uploads/.htaccess

  • El formulari no pot convertir-se en un relay de spam

    S'eliminen salts de línia i caràcters de control abans que cap valor toqui una capçalera de correu. Sense aquest sanejament, un nom amb CRLF permet injectar capçaleres (un Bcc:, per exemple) i fer servir el formulari per enviar correu a tercers.

    server/api/contact.php

  • Exportacions CSV sense injecció de formules

    Excel i LibreOffice executen com a formula qualsevol cel·la que comenci per =, +, - o @. Com que la safata exporta text escrit per desconeguts, aquestes cel·les es prefixen perquè es tractin com a text i no com a codi en obrir el fitxer.

    server/admin/partials/layout.php

04 / Analítica pròpia i privacitat

Saber quanta gent hi entra no exigeix muntar el perfil de ningú. L'analítica és pròpia i desa el mínim.

  • L'adreça IP no s'emmagatzema

    Es desa un SHA-256 de la IP amb una sal del servidor. Serveix per comptar visitants únics aproximats i per frenar floods, però no permet reconstruir l'adreça ni creuar-la amb cap altra base de dades.

    server/api/visit.php

  • Sense galetes de seguiment i amb Do Not Track respectat

    No hi ha bàner de consentiment perquè no hi ha res a consentir. Si el navegador envia el senyal Do Not Track, la visita ni tan sols es registra.

  • La sessió viu i mor amb la pestanya

    Per saber si dues pàgines vistes són la mateixa visita, el navegador desa un identificador aleatori a sessionStorage (no localStorage, no una galeta). Tècnicament segueix sent "emmagatzematge a l'equip" sota l'ePrivacy, però en morir en tancar la pestanya i no combinar-se mai amb una altra visita, encaixa en l'excepció de mesura d'audiència que reconeixen la majoria d'autoritats de protecció de dades de la UE: per això no canvia la resposta d'aquí dalt.

    src/scripts/analytics.ts

  • Del referrer es descarta la query

    Només es conserva esquema, host i ruta. Els paràmetres d'una URL aliena solen portar identificadors de sessió o de campanya, i no hi ha cap raó perquè acabin a la meva base de dades.

    server/api/visit.php

  • Retenció acotada i purga automàtica

    Les visites s'esborren als 400 dies, els intents de login als 90 i el registre d'auditoria a l'any. La purga va incorporada al mateix flux, sense dependre d'un cron que ningú no recorda revisar.

    server/api/visit.php

05 / SEO tècnic

Un portfolio que no apareix no serveix de res. El posicionament aquí és una qüestió d'estructura, no de repetir paraules clau.

  • Un únic graf de dades estructurades

    Person, WebSite, ProfilePage, BreadcrumbList i FAQPage viatgen en un sol bloc JSON-LD enllaçat per @id, en comptes de blocs solts que poden contradir-se entre ells. Cada article del blog hi afegeix el seu propi BlogPosting.

    src/components/Seo.astro

  • 410 Gone en lloc de 404 per al que s’ha retirat

    Dos projectes es van retirar del lloc amb les URL ja indexades. Un 404 diu al cercador que la pàgina podria tornar, i la reintenta durant setmanes; un 410 li diu que la retirada és deliberada i definitiva, i la treu de l'índex força abans.

    server/.htaccess

  • hreflang reciproc, i només on hi ha traducció

    Cada pàgina traduïda declara totes les seves germanes i un x-default. Les que existeixen en un sol idioma no en declaren cap: anunciar un alternatiu que no existeix trenca el grup sencer, i Google llavors el descarta del tot.

    src/pages/sitemap.xml.ts

  • SEO local amb senyals coherents

    Badalona i Barcelona apareixen al títol, a la descripció, a les etiquetes geo i dins del bloc Person amb address, homeLocation i areaServed. El senyal importa quan és el mateix a tot arreu, no quan es repeteix en un sol lloc.

    src/config.ts

06 / Llegible per a models de llenguatge

Cada cop es pregunta més a un assistent i menys a un cercador. Si la resposta sobre mi l'ha de donar una IA, prefereixo que llegeixi dades ordenades i no que improvisi.

  • llms.txt generat a cada compilació

    Un resum en text pla seguint la convenció de llmstxt.org, sense HTML ni soroll. Surt de les mateixes fonts que alimenten el web, així que no pot quedar desfasat respecte del que es veu a la pantalla.

    src/pages/llms.txt.ts

  • Política explicita per a rastrejadors d’IA

    GPTBot, ClaudeBot, PerplexityBot, Google-Extended i Applebot tenen permís escrit. Els rastrejadors de SEO comercial, que només consumeixen amplada de banda de l'allotjament compartit, estan bloquejats. És una decisió presa, no el resultat de no haver tocat el fitxer.

    public/robots.txt

  • Divulgació de vulnerabilitats per RFC 9116

    Un /.well-known/security.txt amb contacte, idiomes i data de caducitat. És el primer que busca qui troba una fallada i la vol reportar bé; no tenir-lo és la raó habitual que un avis acabi en un formulari generic o enlloc.

    public/.well-known/security.txt

  • Anàlisi estàtica a cada push

    CodeQL cobreix el frontend en TypeScript i JavaScript. Com que CodeQL no analitza PHP, el backend el revisa Semgrep amb regles d'injecció, XSS i secrets, i tots dos resultats arriben en format SARIF a la mateixa pestanya. Dependabot vigila les dependencies cada setmana.

    .github/workflows/codeql.yml

Cada punt d'aquesta pàgina correspon a codi que pots obrir al repositori. Si trobes res que no quadri, el canal per avisar-me és a /.well-known/security.txt

Mètriques de rendiment (PageSpeed Insights)

66
Rendiment
96
Accessibilitat
92
Bones pràctiques
100
SEO

* Vista Mòbil, mesurada el 23/08/2026 amb Lighthouse contra producció (CDMON, HTTPS i TLS actius). És una foto d'un moment concret, no una garantia permanent: comprova-ho tu mateix si t'interessa.