penpot/docker/images/Dockerfile.frontend
David Barragán Merino 1e8acbb2db 🐳 Cover the inline scripts of the served pages with CSP hashes
The frontend build now emits the sha256 hashes of the inline scripts of every page it writes into resources/public, the image moves them out of the document root, and the entrypoint splices them into the default script-src. This removes one of the two reasons why enforcing mode was not usable.

The hashes are computed on the rendered output rather than on the mustache templates, since the digest covers the exact bytes served between the script tags. All four served pages contribute, not just index.html: challenge.html handles the redirect, render.html is loaded by the exporter in a headless browser, and rasterizer.html is initialised by the frontend itself, so leaving any of them out would have broken those paths under enforcing mode. The storybook previews are excluded because that container does not serve them.

A bundle predating this change yields no hashes and the policy stays as it was, so older bundles keep building.

The three external locations were also passing through the security headers of their upstreams. raw.githubusercontent.com returns its own Content-Security-Policy and both it and fonts.googleapis.com return Strict-Transport-Security. Browsers enforce the intersection of every policy they receive, so the upstream one takes precedence on those responses, and the HSTS one lands on our own host, meaning a deployment that deliberately disables HSTS would get it set anyway by a third party. Hide all three at the proxy.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-18 18:55:19 +02:00

54 lines
2.3 KiB
Docker

FROM dhi.io/nginx:1.31.1-debian13-dev
LABEL maintainer="Penpot <docker@penpot.app>"
ENV DEBIAN_FRONTEND=noninteractive
USER root
RUN set -ex; \
apt-get -qq update; \
apt-get -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade; \
apt-get -qqy -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --no-install-recommends install bash gettext-base passwd; \
rm -rf /var/lib/apt/lists/*; \
groupadd -f -g 1001 penpot; \
useradd -M -u 1001 -s /usr/sbin/nologin -d /opt/penpot -g penpot penpot; \
mkdir -p /opt/data/assets; \
chown -R penpot:penpot /opt/data; \
mkdir -p /etc/nginx/overrides/main.d/; \
mkdir -p /etc/nginx/overrides/http.d/; \
mkdir -p /etc/nginx/overrides/server.d/; \
mkdir -p /etc/nginx/overrides/assets.d/; \
mkdir -p /etc/nginx/overrides/location.d/;
ARG BUNDLE_PATH="./bundle-frontend/"
COPY $BUNDLE_PATH /var/www/app/
COPY ./files/config.js /var/www/app/js/config.js
COPY ./files/nginx.conf.template /tmp/nginx.conf.template
COPY ./files/nginx-resolvers.conf.template /tmp/resolvers.conf.template
COPY ./files/nginx-admin-console-locations.conf.template /tmp/nginx-admin-console-locations.conf.template
COPY ./files/nginx-mcp-locations.conf.template /tmp/nginx-mcp-locations.conf.template
COPY ./files/nginx-security-headers.conf.template /tmp/nginx-security-headers.conf.template
COPY ./files/nginx-mime.types /etc/nginx/mime.types
COPY ./files/nginx-external-locations.conf /etc/nginx/overrides/location.d/external-locations.conf
COPY ./files/nginx-entrypoint.sh /entrypoint.sh
# The CSP hashes of the inline scripts of index.html are emitted by the
# frontend build. Move them out of the document root: nginx must read them,
# the browser has no reason to.
RUN if [ -f /var/www/app/csp-script-hashes.txt ]; then \
mv /var/www/app/csp-script-hashes.txt /etc/nginx/csp-script-hashes.txt; \
else \
echo "WARNING: the frontend bundle does not provide csp-script-hashes.txt" >&2; \
fi
RUN chown -R 1001:0 /var/cache/nginx; \
chmod -R g+w /var/cache/nginx; \
chown -R 1001:0 /etc/nginx; \
chmod -R g+w /etc/nginx; \
chown -R 1001:0 /var/www; \
chmod -R g+w /var/www;
USER penpot:penpot
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]