mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
build(ci): pin base container images, switch npm install to npm ci, and harden EML error handling (#5353)
# Description of Changes This pull request introduces several improvements focused on security and reliability in both the Docker build process and the backend API. The most significant changes are the use of digest-pinned Docker base images to ensure reproducible builds, safer handling of user-provided filenames in error messages, and a switch to more reliable dependency installation in CI workflows. **Docker image security and reproducibility:** * All Dockerfiles now use digest-pinned base images (e.g., `node:20-alpine@sha256:...`, `gradle:8.14-jdk21@sha256:...`, `alpine:3.22.1@sha256:...`, `nginx:alpine@sha256:...`) to guarantee build consistency and protect against upstream image changes. [[1]](diffhunk://#diff-f8faae0938488156cf26e9322ffdf755deaa8770a7ac8c524dd6126c19548888L5-R5) [[2]](diffhunk://#diff-f8faae0938488156cf26e9322ffdf755deaa8770a7ac8c524dd6126c19548888L18-R18) [[3]](diffhunk://#diff-f8faae0938488156cf26e9322ffdf755deaa8770a7ac8c524dd6126c19548888L38-R38) [[4]](diffhunk://#diff-2f5cd3ad965c86a7a5b4af6e0513ad294e0426644d9f5b5358dfb16a2ef995a7L5-R5) [[5]](diffhunk://#diff-2f5cd3ad965c86a7a5b4af6e0513ad294e0426644d9f5b5358dfb16a2ef995a7L18-R18) [[6]](diffhunk://#diff-2f5cd3ad965c86a7a5b4af6e0513ad294e0426644d9f5b5358dfb16a2ef995a7L37-R37) [[7]](diffhunk://#diff-e9edf3a05475d0721a0e65be1ba0eeb162ae972891b0f6d7e1285687efab1de0L9-R9) [[8]](diffhunk://#diff-fa0700cfd7d90d832649eb1d0503904564bb3b28c48972be7d9f17e4ce32a3dcL9-R9) [[9]](diffhunk://#diff-2e766aaf0c87e7b8a62d2a2986f6999c38cc35f677479e31b77d1b427c7aeef7L5-R5) [[10]](diffhunk://#diff-1726db0cbef194c9be3cba9825c0794802b154e15e4c892c1544d0aace03e037L5-R5) [[11]](diffhunk://#diff-c1b6dd504a16fc68cd064baf9cf07d9dd31da56eb55de69601844ab03a5ae319L5-R5) [[12]](diffhunk://#diff-2fc7fcfcfdbb617dd8fbb6b1a2ea5709f9018d618d13942cb33d3e0ed127df16L5-R5) [[13]](diffhunk://#diff-2fc7fcfcfdbb617dd8fbb6b1a2ea5709f9018d618d13942cb33d3e0ed127df16L39-R39) [[14]](diffhunk://#diff-759e94102d21fe6f9bde8ddb0b4f95b5d5cd214b0355ea0419d3ea6c09e8ffbfL2-R2) [[15]](diffhunk://#diff-759e94102d21fe6f9bde8ddb0b4f95b5d5cd214b0355ea0419d3ea6c09e8ffbfL19-R19) **Backend API security:** * In `ConvertEmlToPDF.java`, error messages now escape user-provided filenames using `HtmlUtils.htmlEscape`, preventing potential XSS vulnerabilities when displaying error messages that include filenames. [[1]](diffhunk://#diff-45d22a96bae3e8a746b7fb2c39e25c80aee0bf733b528a3517db8fdd2a3d25cdR13) [[2]](diffhunk://#diff-45d22a96bae3e8a746b7fb2c39e25c80aee0bf733b528a3517db8fdd2a3d25cdR156-R170) **CI/CD reliability:** * All GitHub Actions workflows (`multiOSReleases.yml`, `releaseArtifacts.yml`, `tauri-build.yml`) now use `npm ci` instead of `npm install` for frontend dependency installation, ensuring clean, reproducible installs that match the lockfile. [[1]](diffhunk://#diff-895b214ee023c8c26048a2a3b946cfb1ebc4f26fbc8a9c2fa54b77c12e763b6bL271-R271) [[2]](diffhunk://#diff-699ff98fe113446c403eb07daf16dd1966c2a047ab0b9f7e38fd695d079f7dddL177-R177) [[3]](diffhunk://#diff-b34ab107dd4bc92075b2e89b6f16e4a2813e267ca7c2afebdb1931a0a3900d5aL177-R177) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
@@ -268,7 +268,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
# DigiCert KeyLocker Setup (Cloud HSM)
|
# DigiCert KeyLocker Setup (Cloud HSM)
|
||||||
- name: Setup DigiCert KeyLocker
|
- name: Setup DigiCert KeyLocker
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
# DigiCert KeyLocker Setup (Cloud HSM)
|
# DigiCert KeyLocker Setup (Cloud HSM)
|
||||||
- name: Setup DigiCert KeyLocker
|
- name: Setup DigiCert KeyLocker
|
||||||
|
|||||||
+10
-5
@@ -10,6 +10,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.springframework.web.util.HtmlUtils;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -152,20 +153,24 @@ public class ConvertEmlToPDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull String buildErrorMessage(Exception e, String originalFilename) {
|
private static @NotNull String buildErrorMessage(Exception e, String originalFilename) {
|
||||||
|
String safeFilename = HtmlUtils.htmlEscape(originalFilename);
|
||||||
|
String exceptionMessage = e.getMessage();
|
||||||
|
String safeExceptionMessage =
|
||||||
|
exceptionMessage == null ? "Unknown error" : HtmlUtils.htmlEscape(exceptionMessage);
|
||||||
String errorMessage;
|
String errorMessage;
|
||||||
if (e.getMessage() != null && e.getMessage().contains("Invalid EML")) {
|
if (exceptionMessage != null && exceptionMessage.contains("Invalid EML")) {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
"Invalid EML file format. Please ensure you've uploaded a valid email"
|
"Invalid EML file format. Please ensure you've uploaded a valid email"
|
||||||
+ " file ("
|
+ " file ("
|
||||||
+ originalFilename
|
+ safeFilename
|
||||||
+ ").";
|
+ ").";
|
||||||
} else if (e.getMessage() != null && e.getMessage().contains("WeasyPrint")) {
|
} else if (exceptionMessage != null && exceptionMessage.contains("WeasyPrint")) {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
"PDF generation failed for "
|
"PDF generation failed for "
|
||||||
+ originalFilename
|
+ safeFilename
|
||||||
+ ". This may be due to complex email formatting.";
|
+ ". This may be due to complex email formatting.";
|
||||||
} else {
|
} else {
|
||||||
errorMessage = "Conversion failed for " + originalFilename + ": " + e.getMessage();
|
errorMessage = "Conversion failed for " + safeFilename + ": " + safeExceptionMessage;
|
||||||
}
|
}
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Supports MODE parameter: BOTH (default), FRONTEND, BACKEND
|
# Supports MODE parameter: BOTH (default), FRONTEND, BACKEND
|
||||||
|
|
||||||
# Stage 1: Build Frontend
|
# Stage 1: Build Frontend
|
||||||
FROM node:20-alpine AS frontend-build
|
FROM node:20-alpine@sha256:658d0f63e501824d6c23e06d4bb95c71e7d704537c9d9272f488ac03a370d448 AS frontend-build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ COPY frontend .
|
|||||||
RUN DISABLE_ADDITIONAL_FEATURES=false VITE_API_BASE_URL=/ npm run build
|
RUN DISABLE_ADDITIONAL_FEATURES=false VITE_API_BASE_URL=/ npm run build
|
||||||
|
|
||||||
# Stage 2: Build Backend (server-only JAR - no UI)
|
# Stage 2: Build Backend (server-only JAR - no UI)
|
||||||
FROM gradle:8.14-jdk21 AS backend-build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS backend-build
|
||||||
|
|
||||||
COPY build.gradle .
|
COPY build.gradle .
|
||||||
COPY settings.gradle .
|
COPY settings.gradle .
|
||||||
@@ -35,7 +35,7 @@ RUN DISABLE_ADDITIONAL_FEATURES=false \
|
|||||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
# Stage 3: Final unified image
|
# Stage 3: Final unified image
|
||||||
FROM alpine:3.22.1
|
FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Supports MODE parameter: BOTH (default), FRONTEND, BACKEND
|
# Supports MODE parameter: BOTH (default), FRONTEND, BACKEND
|
||||||
|
|
||||||
# Stage 1: Build Frontend
|
# Stage 1: Build Frontend
|
||||||
FROM node:20-alpine AS frontend-build
|
FROM node:20-alpine@sha256:658d0f63e501824d6c23e06d4bb95c71e7d704537c9d9272f488ac03a370d448 AS frontend-build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ COPY frontend .
|
|||||||
RUN DISABLE_ADDITIONAL_FEATURES=true VITE_API_BASE_URL=/ npm run build
|
RUN DISABLE_ADDITIONAL_FEATURES=true VITE_API_BASE_URL=/ npm run build
|
||||||
|
|
||||||
# Stage 2: Build Backend
|
# Stage 2: Build Backend
|
||||||
FROM gradle:8.14-jdk21 AS backend-build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS backend-build
|
||||||
|
|
||||||
COPY build.gradle .
|
COPY build.gradle .
|
||||||
COPY settings.gradle .
|
COPY settings.gradle .
|
||||||
@@ -34,7 +34,7 @@ RUN DISABLE_ADDITIONAL_FEATURES=true \
|
|||||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
# Stage 3: Final unified ultra-lite image
|
# Stage 3: Final unified ultra-lite image
|
||||||
FROM alpine:3.22.1
|
FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# ========================================
|
# ========================================
|
||||||
# STAGE 1: Build stage - Alpine with Gradle
|
# STAGE 1: Build stage - Alpine with Gradle
|
||||||
# ========================================
|
# ========================================
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS build
|
||||||
|
|
||||||
COPY build.gradle .
|
COPY build.gradle .
|
||||||
COPY settings.gradle .
|
COPY settings.gradle .
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# ========================================
|
# ========================================
|
||||||
# STAGE 1: Build stage - Gradle
|
# STAGE 1: Build stage - Gradle
|
||||||
# ========================================
|
# ========================================
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS build
|
||||||
|
|
||||||
COPY build.gradle .
|
COPY build.gradle .
|
||||||
COPY settings.gradle .
|
COPY settings.gradle .
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# ========================================
|
# ========================================
|
||||||
# STAGE 1: Build stage - Gradle
|
# STAGE 1: Build stage - Gradle
|
||||||
# ========================================
|
# ========================================
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS build
|
||||||
|
|
||||||
COPY build.gradle .
|
COPY build.gradle .
|
||||||
COPY settings.gradle .
|
COPY settings.gradle .
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Single JAR contains both frontend and backend
|
# Single JAR contains both frontend and backend
|
||||||
|
|
||||||
# Stage 1: Build application with embedded frontend
|
# Stage 1: Build application with embedded frontend
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS build
|
||||||
|
|
||||||
# Install Node.js and npm for frontend build
|
# Install Node.js and npm for frontend build
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Single JAR contains both frontend and backend with extra fonts for air-gapped environments
|
# Single JAR contains both frontend and backend with extra fonts for air-gapped environments
|
||||||
|
|
||||||
# Stage 1: Build application with embedded frontend
|
# Stage 1: Build application with embedded frontend
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS build
|
||||||
|
|
||||||
# Install Node.js and npm for frontend build
|
# Install Node.js and npm for frontend build
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Single JAR contains both frontend and backend with minimal dependencies
|
# Single JAR contains both frontend and backend with minimal dependencies
|
||||||
|
|
||||||
# Stage 1: Build application with embedded frontend
|
# Stage 1: Build application with embedded frontend
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14-jdk21@sha256:051d9a116793bdc5175a3f97a545718b750489eee85a7da20913c8a53f722a72 AS build
|
||||||
|
|
||||||
# Install Node.js and npm for frontend build
|
# Install Node.js and npm for frontend build
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
@@ -36,7 +36,7 @@ RUN DISABLE_ADDITIONAL_FEATURES=true \
|
|||||||
./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
# Stage 2: Runtime image
|
# Stage 2: Runtime image
|
||||||
FROM alpine:3.22.1
|
FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Frontend Dockerfile - React/Vite application
|
# Frontend Dockerfile - React/Vite application
|
||||||
FROM node:20-alpine AS build
|
FROM node:20-alpine@sha256:658d0f63e501824d6c23e06d4bb95c71e7d704537c9d9272f488ac03a370d448 AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ COPY frontend .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine@sha256:8491795299c8e739b7fcc6285d531d9812ce2666e07bd3dd8db00020ad132295
|
||||||
|
|
||||||
# Copy built files from build stage
|
# Copy built files from build stage
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|||||||
Reference in New Issue
Block a user