Seguridad en GitHub Pages con CSP

GitHub Pages es una de las opciones más populares para alojar sitios estáticos de forma gratuita. Lo puedes usar para documentación, portfolios, landing pages e incluso para aplicaciones web completas generadas con frameworks como Astro, Next.js o Hugo.

Últimamente, además, estoy haciendo bastante uso de GitHub Pages para side projects o demos en las que simplemente necesito servir contenido estático sin complicarme demasiado. Y es que ya te conté en otro artículo que incluso puedes tener repositorios privados que desplieguen en GitHub Pages y que ese contenido sea público… algo que, sinceramente, me está resultando extremadamente útil en mi día a día.

Pero claro… aquí viene la parte que no deberíamos pasar por alto 👀

Porque aunque sea “solo estático”, seguimos exponiendo una aplicación web al mundo. Y eso implica que la seguridad sigue siendo importante.

En este artículo te voy a contar qué puedes (y qué no puedes) hacer para mejorar la seguridad de un sitio alojado en GitHub Pages, centrándome en una de las herramientas más potentes que tenemos a nuestro alcance: Content Security Policy (CSP).

¿Qué es GitHub Pages y cuáles son sus limitaciones?

GitHub Pages es un servicio de hosting estático que sirve archivos HTML, CSS y JavaScript directamente desde un repositorio de GitHub. No hay servidor backend, no hay base de datos, no hay procesamiento del lado del servidor. Solo archivos estáticos.

Esto tiene muchas ventajas (rapidez, simplicidad, coste cero), pero también una limitación importante para la seguridad: no puedes configurar headers HTTP personalizados. Y los headers HTTP son el mecanismo principal para aplicar políticas de seguridad en la web.

Dicho esto, GitHub Pages sí aplica algunas protecciones por defecto:

  • HTTPS forzado con Strict-Transport-Security
  • Certificados TLS gestionados automáticamente
  • Infraestructura de CDN global

Pero hay muchas cosas que no puedes controlar: X-Frame-Options, Permissions-Policy, o el propio Content-Security-Policy como header. Para eso tenemos un plan B.

¿Qué es Content Security Policy (CSP)?

CSP es un estándar de seguridad web que permite definir qué recursos puede cargar tu página. Funciona como una lista blanca: solo se ejecutan scripts, se cargan estilos, fuentes o se hacen peticiones de red a los orígenes que tú autorices explícitamente.

La forma habitual de aplicar CSP es mediante un header HTTP:

Content-Security-Policy: default-src 'self'; script-src 'self';

Pero como en GitHub Pages no podemos configurar headers, usamos la alternativa: una etiqueta <meta> en el <head> del HTML:

<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self'; script-src 'self' 'unsafe-inline';"
/>

El navegador lee esta etiqueta y aplica las mismas restricciones que si viniera como header. No importa si tu hosting es GitHub Pages o cualquier otro. Es el navegador quien lo interpreta.

¿De qué te protege CSP?

El principal ataque que mitiga CSP es Cross-Site Scripting (XSS). Si un atacante consigue inyectar un <script src="https://malicious.com/steal.js"> en tu página, el navegador lo bloqueará porque malicious.com no está en tu lista de orígenes permitidos.

Además, CSP ayuda contra:

  • Exfiltración de datos: un script malicioso no puede enviar datos a servidores no autorizados si connect-src está restringido.
  • Inyección de estilos: con style-src puedes evitar que se carguen CSS externos maliciosos.
  • Carga de recursos no deseados: controlas de dónde vienen las imágenes (img-src), las fuentes (font-src) y todo lo demás.

Ejemplo práctico: un sitio Astro en GitHub Pages

Recientemente construí una aplicación de encuestas anónimas hecha con Astro + React, desplegada en GitHub Pages y estas son las decisiones de seguridad que tomé:

La política CSP que apliqué

<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self';
    script-src 'self' 'unsafe-inline';
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
    font-src https://fonts.gstatic.com;
    img-src 'self' data:;
    connect-src 'self' https://api.web3forms.com https://api.fpjs.io;
    base-uri 'self';
    form-action 'none';"
/>

Si echamos un vistazo a cada una de las directivas:

DirectivaQué permitePor qué
default-src 'self'Solo recursos del propio dominioPolítica restrictiva por defecto
script-src 'self' 'unsafe-inline'Scripts propios + inlineAstro/React necesitan inline scripts para la hidratación
style-src 'self' 'unsafe-inline' fonts.googleapis.comEstilos propios, inline y Google FontsTailwind genera estilos inline; usamos Google Fonts
font-src fonts.gstatic.comFuentes de GoogleLas fuentes se sirven desde gstatic
img-src 'self' data:Imágenes propias y data URIsLos SVG inline usan data:
connect-src 'self' api.web3forms.com api.fpjs.ioFetch/XHR a servicios autorizadosWeb3Forms para emails, FingerprintJS para deduplicación
base-uri 'self'Solo base URI propiaEvita ataques que cambien el <base> del HTML
form-action 'none'Bloquea envío de forms HTMLUsamos fetch(), no formularios nativos

Otras meta etiquetas de seguridad

Además del CSP, añadí estas dos:

<!-- Evita enviar el referrer a sitios externos -->
<meta name="referrer" content="no-referrer" />
<!-- Evita que el navegador adivine el MIME type -->
<meta http-equiv="X-Content-Type-Options" content="nosniff" />

Cómo probarlo

Para comprobar que esto que hemos configurado tiene su efecto puedes abrir las DevTools de tu navegador y en la sección de consola podrías intentar añadir algo como esto:

const s = document.createElement('script');
s.src = 'https://evil.example.com/test.js';
document.head.appendChild(s);

Si está bien configurado debería de devolver algo como lo siguiente:

Deberías ver un error de CSP en la consola confirmando que script-src bloquea el recurso. Si no aparece error, la política no se está aplicando.

También es posible confirmar la configuración aplicada yendo a la sección Application > Frames > top y ahí podrás ver una sección sobre la configuración relacionada con Content Security Policy (CSP)

Lo que NO puedes hacer con meta tags

Hay directivas CSP y headers de seguridad que solo funcionan como headers HTTP y no se pueden aplicar via <meta>:

  • frame-ancestors: controla quién puede embeber tu página en un iframe. En GitHub Pages no puedes configurarlo.
  • Permissions-Policy: restringe el acceso a APIs del navegador (cámara, micrófono, geolocalización). No existe como meta tag.
  • X-Frame-Options: la versión legacy de frame-ancestors. Tampoco funciona como meta tag.

Si necesitas control total sobre estos headers, tendrás que considerar un hosting que permita configurar headers HTTP personalizados.

Conclusión

GitHub Pages es una opción más que suficiente para sitios estáticos, y aunque tiene limitaciones al no poder configurar headers HTTP, puedes hacer bastante con meta tags. Un CSP bien configurado via <meta> te protege contra XSS, controla qué recursos se cargan y limita las conexiones salientes.

No esperes a tener un hosting «de pago» para aplicar buenas prácticas de seguridad. Con unas pocas líneas en tu <head> puedes mejorar significativamente la postura de seguridad de tu sitio.

¡Nos vemos 👋🏻!

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.