Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danizd/urbanviable/llms.txt

Use this file to discover all available pages before exploring further.

UrbanViable has a fundamentally small attack surface. There is no user authentication, no form submission, no database, and no server-side business logic. The scouting tool’s core interaction — weighting variables with sliders and filtering census sections — happens entirely in the browser. Slider values never leave the client. No user-generated input is sent to the server; the only requests the frontend makes are tile fetches and a single status JSON read. This eliminates the entire category of server-side injection vulnerabilities that are relevant to more complex applications.

CORS

When a /tiles/ reverse-proxy location is added to nginx/conf.d/default.conf, CORS headers should be set there to restrict tile consumption to the frontend’s own origin. In the current configuration TileServer GL is reachable on host port 8080 directly; adding a Nginx proxy layer is recommended before any production domain goes live. The CORS snippet for that location is:
location /tiles/ {
    proxy_pass http://urbanviable-tiles:8080/data/;

    # Only the frontend's own domain may consume tiles
    add_header 'Access-Control-Allow-Origin' 'https://tu-dominio.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
}

Allowed origins by environment

EnvironmentAccess-Control-Allow-Origin
Productionhttps://urbanviable.movilab.es
Local development (Vite dev server)http://localhost:5173
Never use Access-Control-Allow-Origin: * in production. A wildcard origin allows any website on the internet to embed your tile source in a map and consume your server’s bandwidth without restriction. Set CORS_ORIGIN in .env to the exact domain of your frontend, and ensure the value is reflected in nginx/conf.d/default.conf.

HTTPS / TLS

AspectDecision
Certificate providerLet’s Encrypt (free, automatic renewal via Certbot) or Cloudflare origin certificate
SSL terminationNginx (urbanviable-web container)
HTTP → HTTPS redirectAutomatic — the HTTP server block issues a 301 to the HTTPS equivalent URL
HSTSEnabled — Strict-Transport-Security: max-age=31536000; includeSubDomains
TLS on TileServer GLNot applicable — TileServer GL serves plain HTTP on port 8080; all public-facing TLS is handled by Nginx
TLS certificates are bind-mounted into the Nginx container from ./certs/ as a read-only volume (/etc/letsencrypt:ro). The certs/ directory is excluded from Git via .gitignore — certificate files must never be committed to version control.

HTTP security headers

The following headers are applied to all responses from the urbanviable-web container:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
HeaderValueEffect
Strict-Transport-Securitymax-age=31536000; includeSubDomainsBrowsers enforce HTTPS-only for one year; prevents SSL stripping and protocol downgrade attacks
X-Content-Type-OptionsnosniffPrevents the browser from MIME-sniffing a response away from the declared Content-Type
X-Frame-OptionsDENYBlocks the application from being embedded in an <iframe> on any other origin, preventing clickjacking
X-XSS-Protection1; mode=blockActivates the legacy XSS filter in older browsers; modern browsers rely on CSP instead

Content-Security-Policy

The CSP restricts what resources the browser is permitted to load, reducing the blast radius of any XSS vulnerability:
add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'unsafe-inline';
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
    img-src 'self' data: https://*.basemaps.cartocdn.com https://*.openstreetmap.org;
    connect-src 'self' https://tu-dominio.com;
    font-src 'self' https://fonts.gstatic.com;
" always;
DirectiveAllowed sourcesReason
default-src'self'Baseline restriction: only same-origin resources unless overridden
script-src'self' 'unsafe-inline'Inline scripts are required by the Create React App bundle
style-src'self' 'unsafe-inline' fonts.googleapis.comInline styles used by MapLibre GL; Google Fonts stylesheet
img-src'self' data: *.basemaps.cartocdn.com *.openstreetmap.orgMapLibre GL loads basemap raster tiles from CartoDB (Dark Matter style); data: URIs used for sprites
connect-src'self' https://tu-dominio.comXHR/fetch is restricted to the same origin and the tile/status endpoints
font-src'self' fonts.gstatic.comGoogle Fonts font files
The img-src directive must include CartoDB basemap tile domains (*.basemaps.cartocdn.com) because MapLibre GL JS requests raster tiles for the background map as <img>-equivalent network requests. If you switch basemap providers, update this directive accordingly.

Residual attack vectors

Despite its minimal server-side footprint, a few low-probability vectors exist:
VectorRisk levelMitigation
Scraping or bulk-downloading tilesLow–mediumCache-Control reduces repeated server load; Cloudflare rate limiting can be added in front of Nginx if scraping becomes a concern
Direct access to TileServer GLLowTileServer GL binds to host port 8080. Restrict access at the firewall level (OCI Security List) to deny public ingress on port 8080, so external traffic can only reach tiles through Nginx on port 3002
Manipulation of last_update.jsonVery lowThe file is mounted into the Nginx container as a read-only bind mount (:ro); the container cannot write to it
URL parameter injection in tile requestsVery lowNginx validates the /tiles/ path pattern before proxying; TileServer GL independently rejects any request that does not match a valid {z}/{x}/{y}.pbf pattern
Sensitive data exposure via tilesNoneTiles contain only public, aggregated statistical data (INE census and income data); there is no PII, no user data, and no proprietary business data in the tile set

ETL security

The ETL pipeline runs on a developer workstation, not on the production server. This architectural decision removes the most common server-side risks associated with data pipelines:
  • No remote code execution surface on the server for ETL errors or dependency vulnerabilities
  • No Python environment, pip packages, or ETL scripts exposed to the internet
  • Data files travel to the server only via a direct scp transfer from the developer’s machine
ETL riskMitigation
Corrupted or malformed download from INEVerify file size and structure before running process_data.py; review polygon count and null-value report before uploading
Uploading an .mbtiles to the wrong serverDocument and use a consistent SSH host alias; confirm the destination path before transfer
PII leaking through raw data filesINE data is statistically aggregated — no individual records, no names, no personal identifiers. raw/ is excluded from Git due to file size, not confidentiality

GDPR / LOPDGDD applicability

UrbanViable does not collect, store, or process any personal data:
  • No user accounts or login
  • No analytics or tracking cookies
  • No server-side logging of which zones a user views
  • No transmission of slider weights to the server
  • All tile data is derived from publicly available, aggregated statistical datasets (INE)
GDPR and LOPDGDD do not apply to the current MVP. If user accounts or analytics are added in the future, a full privacy impact assessment will be required.

Cache and tile security

# Tile responses are cached aggressively — tiles are static between ETL runs
add_header Cache-Control "public, max-age=86400";  # 24 hours
The 24-hour browser cache means tiles are not re-fetched on every page load, reducing server load and improving perceived performance. The trade-off is that after an ETL update, users may see stale tiles for up to 24 hours. To force immediate cache invalidation after an ETL update, add a version query parameter to the tile URL before rebuilding the frontend:
// frontend/src/constants/variables.js
export const TILE_URL = `${import.meta.env.REACT_APP_TILE_URL}?v=2026`;
The ?v=2026 parameter is ignored by TileServer GL but causes the browser to treat the URL as a new, uncached resource. Increment the version number with each new annual ETL run.

Build docs developers (and LLMs) love