From faec0e9fcf984060a5bbd7bf64d5ce9501e4eff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Felix=20=C5=A0ulc?= Date: Sun, 25 Oct 2026 11:53:10 +0100 Subject: [PATCH] Test: check untrusted HTML runs no JS and fetches nothing Render probe pages (inline script, CDN script/style, public image, file:// iframe) next to identical controls; the PNGs must be byte-identical. Run by make test-isolation and in the CI smoke job. --- .github/workflows/docker.yml | 3 +++ Makefile | 7 +++++-- test/isolation.sh | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100755 test/isolation.sh diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 44ca440..5c4609c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -80,6 +80,9 @@ jobs: - name: Render a page run: make smoke DOCKER_IMAGE=dockette/docshot:test + - name: Check HTML isolation + run: make test-isolation DOCKER_IMAGE=dockette/docshot:test + # Informational: the base image (LibreOffice, Chromium) carries CVEs we cannot fix here; # the weekly rebuild picks up upstream fixes. Pinned by digest: scanners are a supply-chain target too. - name: Scan image (Trivy) diff --git a/Makefile b/Makefile index c4dad2e..5088380 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ DOCKER_IMAGE ?= dockette/docshot:latest VERSION := $(shell cat VERSION) .DEFAULT_GOAL := up -.PHONY: test-formats build run up down restart logs ps validate test test-params test-all formats test-unit lint smoke version release +.PHONY: test-formats build run up down restart logs ps validate test test-params test-all formats test-unit lint smoke test-isolation version release build: ## Build the public image (docker buildx) docker buildx build --build-arg VERSION=$(VERSION) -t ${DOCKER_IMAGE} . @@ -21,7 +21,7 @@ test-unit: ## Go vet + unit tests (in a golang container) lint: ## gofmt, staticcheck, govulncheck, shellcheck, hadolint $(GO_DOCKER) sh -c 'test -z "$$(gofmt -l .)" && go run honnef.co/go/tools/cmd/staticcheck@452d5bb86b45 -tags nodynamic ./... && go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 -tags nodynamic ./...' - docker run --rm -v $(CURDIR):/mnt -w /mnt koalaman/shellcheck:stable -S warning docshot.sh test/formats/gen.sh + docker run --rm -v $(CURDIR):/mnt -w /mnt koalaman/shellcheck:stable -S warning docshot.sh test/formats/gen.sh test/isolation.sh docker run --rm -i -v $(CURDIR)/.hadolint.yaml:/.config/hadolint.yaml:ro hadolint/hadolint < Dockerfile smoke: ## Start the public image, wait for /readyz, render Markdown to PNG @@ -30,6 +30,9 @@ smoke: ## Start the public image, wait for /readyz, render Markdown t ct=$$(printf '# docshot\n\nsmoke test\n' | curl -sS -m 120 -o /dev/null -w '%{http_code} %{content_type}' --data-binary @- 'http://localhost:39994/upload?name=smoke.md'); \ docker rm -f docshot-smoke >/dev/null; echo "smoke: $$ct"; [ "$$ct" = "200 image/png" ] +test-isolation: ## Untrusted HTML: no JavaScript, no remote fetches (starts ${DOCKER_IMAGE}) + test/isolation.sh ${DOCKER_IMAGE} + version: ## Print VERSION @echo $(VERSION) diff --git a/test/isolation.sh b/test/isolation.sh new file mode 100755 index 0000000..44c67b0 --- /dev/null +++ b/test/isolation.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# Untrusted HTML must neither run JavaScript nor fetch anything (CDN scripts/styles, public images, file://). +# Each probe page is rendered next to an identical page without the probe: the PNGs must be byte-identical. +# Usage: test/isolation.sh [image] (default dockette/docshot:latest) +IMAGE=${1:-dockette/docshot:latest} +PORT=39995 +NAME=docshot-isolation +DIR=$(mktemp -d) +trap 'docker rm -f $NAME >/dev/null 2>&1; rm -rf "$DIR"' EXIT + +docker rm -f $NAME >/dev/null 2>&1 +docker run -d --name $NAME -p $PORT:3000 "$IMAGE" >/dev/null || exit 1 +i=0 +until curl -fsS -m 2 "http://localhost:$PORT/readyz" >/dev/null 2>&1; do + i=$((i + 1)); [ $i -gt 60 ] && { echo "isolation: docshot did not become ready"; exit 1; } + sleep 2 +done + +page() { printf '%s

isolation

%s' "$1" "$2"; } +render() { curl -sS -m 120 --data-binary @- "http://localhost:$PORT/upload?name=p.html" -o "$DIR/$1.png"; } + +fail=0 +probe() { # name, probe head, probe body, control head, control body + page "$2" "$3" | render "$1-probe" + page "$4" "$5" | render "$1-control" + if [ -s "$DIR/$1-control.png" ] && cmp -s "$DIR/$1-probe.png" "$DIR/$1-control.png"; then + echo "isolation: $1 ok" + else + echo "isolation: $1 FAILED (probe changed the rendered page)"; fail=1 + fi +} + +probe script '' '' '' '' +probe cdn-script '' '' '' '' +probe cdn-style '' '' '' '' +probe remote-image '' '' '' '' +probe file-iframe '' '' '' '' + +exit $fail