diff --git a/.github/workflows/docker-build-daily.yml b/.github/workflows/docker-build-daily.yml new file mode 100644 index 00000000..37c836f6 --- /dev/null +++ b/.github/workflows/docker-build-daily.yml @@ -0,0 +1,38 @@ +name: Daily Docker Build + +on: + workflow_dispatch: + schedule: + - cron: "0 2 * * *" + +jobs: + build-and-scan: + name: Build and scan Docker image (daily) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image from fresh base layers + run: | + docker buildx build \ + --pull \ + --load \ + --tag cyberchef:${{ github.sha }} \ + -f Dockerfile . + + - name: Scan image for High/Critical issues (fails on detection) + uses: aquasecurity/trivy-action@0.24.0 + with: + image-ref: cyberchef:${{ github.sha }} + format: table + vuln-type: "os,library" + severity: "CRITICAL,HIGH" + exit-code: "1" + ignore-unfixed: false diff --git a/.github/workflows/docker-build-monthly.yml b/.github/workflows/docker-build-monthly.yml new file mode 100644 index 00000000..f644499d --- /dev/null +++ b/.github/workflows/docker-build-monthly.yml @@ -0,0 +1,38 @@ +name: Monthly Docker Build + +on: + workflow_dispatch: + schedule: + - cron: "0 4 1 * *" + +jobs: + build-and-scan: + name: Build and scan Docker image (monthly) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image from fresh base layers + run: | + docker buildx build \ + --pull \ + --load \ + --tag cyberchef:${{ github.sha }} \ + -f Dockerfile . + + - name: Scan image for High/Critical issues (fails on detection) + uses: aquasecurity/trivy-action@0.24.0 + with: + image-ref: cyberchef:${{ github.sha }} + format: table + vuln-type: "os,library" + severity: "CRITICAL,HIGH" + exit-code: "1" + ignore-unfixed: false diff --git a/.github/workflows/docker-build-weekly.yml b/.github/workflows/docker-build-weekly.yml new file mode 100644 index 00000000..21907bcf --- /dev/null +++ b/.github/workflows/docker-build-weekly.yml @@ -0,0 +1,38 @@ +name: Weekly Docker Build + +on: + workflow_dispatch: + schedule: + - cron: "0 3 * * 1" + +jobs: + build-and-scan: + name: Build and scan Docker image (weekly) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image from fresh base layers + run: | + docker buildx build \ + --pull \ + --load \ + --tag cyberchef:${{ github.sha }} \ + -f Dockerfile . + + - name: Scan image for High/Critical issues (fails on detection) + uses: aquasecurity/trivy-action@0.24.0 + with: + image-ref: cyberchef:${{ github.sha }} + format: table + vuln-type: "os,library" + severity: "CRITICAL,HIGH" + exit-code: "1" + ignore-unfixed: false diff --git a/Dockerfile b/Dockerfile index ba605fd7..b82497b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds # This is because npm "chromedriver" package is not compatiable with all platforms # For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation -FROM --platform=$BUILDPLATFORM node:18-alpine AS builder +FROM --platform=$BUILDPLATFORM node:22-alpine AS builder WORKDIR /app @@ -13,7 +13,7 @@ COPY package-lock.json . # Install dependencies # --ignore-scripts prevents postinstall script (which runs grunt) as it depends on files other than package.json -RUN npm ci --ignore-scripts +RUN npm ci --ignore-scripts --no-audit --no-fund # Copy files needed for postinstall and build COPY . . @@ -30,6 +30,8 @@ RUN npm run build # We are using Github Actions: redhat-actions/buildah-build@v2 which needs manual selection of arch in base image # Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set ARG TARGETPLATFORM -FROM --platform=${TARGETPLATFORM} nginx:stable-alpine AS cyberchef +FROM --platform=${TARGETPLATFORM} nginx:1.27-alpine AS cyberchef -COPY --from=builder /app/build/prod /usr/share/nginx/html/ +COPY --from=builder --chown=nginx:nginx /app/build/prod /usr/share/nginx/html/ + +USER nginx diff --git a/package.json b/package.json index 9c44172b..8c31c1a5 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "@babel/plugin-syntax-import-assertions": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-env": "^7.24.7", - "@babel/runtime": "^7.24.7", + "@babel/runtime": "^7.26.10", + "@babel/helpers": "^7.26.10", "@codemirror/commands": "^6.6.0", "@codemirror/language": "^6.10.2", "@codemirror/search": "^6.5.6", @@ -91,7 +92,7 @@ "terser": "^5.31.1", "webpack": "^5.91.0", "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "5.0.4", + "webpack-dev-server": "^5.11.0", "webpack-node-externals": "^3.0.0", "worker-loader": "^3.0.8" }, @@ -100,11 +101,11 @@ "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", - "@xmldom/xmldom": "^0.8.10", + "@xmldom/xmldom": "^0.9.3", "argon2-browser": "^1.18.0", "arrive": "^2.4.1", "avsc": "^5.7.7", - "bcryptjs": "^2.4.3", + "bcryptjs": "^3.0.3", "bignumber.js": "^9.1.2", "blakejs": "^1.2.1", "bootstrap": "4.6.2", @@ -144,7 +145,7 @@ "json5": "^2.2.3", "jsonata": "^2.0.3", "jsonpath-plus": "^9.0.0", - "jsonwebtoken": "8.5.1", + "jsonwebtoken": "^9.0.2", "jsqr": "^1.4.0", "jsrsasign": "^11.1.0", "kbpgp": "2.1.15", @@ -185,7 +186,7 @@ "utf8": "^3.0.0", "uuid": "^11.1.0", "vkbeautify": "^0.99.3", - "xpath": "0.0.34", + "xpath": "^0.0.35", "xregexp": "^5.1.1", "zlibjs": "^0.3.1" }, diff --git a/scripts/security-fix.sh b/scripts/security-fix.sh index 6772a28d..9b6ec6f9 100755 --- a/scripts/security-fix.sh +++ b/scripts/security-fix.sh @@ -42,7 +42,7 @@ npm install --save-dev @babel/helpers@^7.26.10 || echo -e "${RED}Failed to updat # Update webpack-dev-server (Source code theft vulnerability) echo "3. Updating webpack-dev-server (GHSA-9jgg-88mc-972h)..." -npm install --save-dev webpack-dev-server@^5.2.2 || echo -e "${RED}Failed to update webpack-dev-server${NC}" +npm install --save-dev webpack-dev-server@^5.11.0 || echo -e "${RED}Failed to update webpack-dev-server${NC}" # Update tmp (Symlink vulnerability) echo "4. Updating tmp (GHSA-52f5-9888-hmc6)..." @@ -52,6 +52,18 @@ npm install --save-dev tmp@^0.2.5 || echo -e "${RED}Failed to update tmp${NC}" echo "5. Updating bcryptjs (recommended)..." npm install bcryptjs@^3.0.3 || echo -e "${RED}Failed to update bcryptjs${NC}" +# Update @xmldom/xmldom (multiple advisories) +echo "6. Updating @xmldom/xmldom..." +npm install @xmldom/xmldom@^0.9.3 || echo -e "${RED}Failed to update @xmldom/xmldom${NC}" + +# Update jsonwebtoken (CVE-2022-23529 and other fixes) +echo "7. Updating jsonwebtoken..." +npm install jsonwebtoken@^9.0.2 || echo -e "${RED}Failed to update jsonwebtoken${NC}" + +# Update xpath (ReDoS fixes) +echo "8. Updating xpath..." +npm install xpath@^0.0.35 || echo -e "${RED}Failed to update xpath${NC}" + echo "" echo -e "${YELLOW}🔍 Running npm audit fix...${NC}" npm audit fix || echo -e "${YELLOW}⚠ npm audit fix completed with warnings${NC}" diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 2b2cb096..2ded65f9 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -35,6 +35,7 @@ import { searchKeymap, highlightSelectionMatches } from "@codemirror/search"; +import DOMPurify from "dompurify"; import {statusBar} from "../utils/statusBar.mjs"; import {htmlPlugin} from "../utils/htmlWidget.mjs"; @@ -350,7 +351,12 @@ class OutputWaiter { * @param {string} html */ async setHTMLOutput(html) { - this.htmlOutput.html = html; + const sanitizedHtml = DOMPurify.sanitize(html, { + USE_PROFILES: {html: true}, + FORBID_TAGS: ["script"], + RETURN_TRUSTED_TYPE: false + }); + this.htmlOutput.html = sanitizedHtml; this.htmlOutput.changed = true; // This clears the text output, but also fires a View update which // triggers the htmlWidget to render the HTML. We set the force flag @@ -364,17 +370,6 @@ class OutputWaiter { // Add class to #output-text to change display settings this.outputTextEl.classList.add("html-output"); - - // Execute script sections - const outputHTML = document.getElementById("output-html"); - const scriptElements = outputHTML ? outputHTML.querySelectorAll("script") : []; - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - log.error(err); - } - } } /**