Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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} .
Expand All @@ -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
Expand All @@ -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)

Expand Down
39 changes: 39 additions & 0 deletions test/isolation.sh
Original file line number Diff line number Diff line change
@@ -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 '<!doctype html><html><head><style>body{background:#fff;font:20px sans-serif}</style>%s</head><body><h1>isolation</h1>%s</body></html>' "$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 '' '<script>document.body.style.background="#f00"</script>' '' ''
probe cdn-script '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>' '' '' ''
probe cdn-style '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css">' '' '' ''
probe remote-image '' '<img src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" width="272" height="92">' '' '<img src="data:," width="272" height="92">'
probe file-iframe '' '<iframe src="file:///etc/passwd" width="400" height="100"></iframe>' '' '<iframe src="file:///nonexistent" width="400" height="100"></iframe>'

exit $fail
Loading