#3892·bunkerweb

Default error page is 139 KB with 40 KB of duplicated inline SVG, served on every intercepted error

Author: Ayushsinha322Created Sep 8, 2026Updated Sep 18, 2026
Labelsperformancecore

Affected area

core — the errors plugin, src/common/core/errors/files/error.html.

What happened?

The default error page is a 139,058-byte template, and 91% of it is inline decorative SVG. A significant share of that SVG is literally duplicated: four <svg> blocks are byte-identical copies rendered twice each, which is 40,694 bytes of pure redundancy.

Because INTERCEPTED_ERROR_CODES defaults to 400 401 403 404 405 413 429 500 501 502 503 504, this page is what every intercepted error returns — a 404 for a missing asset, a 429 from rate limiting, a 403 from ModSecurity. Measured on 1.6.9, a 403 renders to a 69,283-byte response body, against 28 bytes for a clean 200 from the same origin.

Static analysis of error.html on dev (unchanged from 1.6.9):

Bytes Share
Whole file 139,058 100%
11 inline <svg> blocks 126,227 91%
Byte-identical duplicates (4 blocks × 2) 40,694 29%

The four duplicated blocks are 20,356 B, 10,588 B, 9,546 B and 204 B, each appearing twice — presumably a light/dark or desktop/mobile variant where the artwork was pasted rather than referenced.

Why it's worth fixing

This is an efficiency issue, not a security one, and I want to be clear about that up front. I did benchmark the deny path, but with USE_LIMIT_REQ, USE_BAD_BEHAVIOR and the blacklist features disabled to isolate it — on stock defaults a single misbehaving source is rate-limited to 2 r/s and banned after 10 blocked requests, so the load numbers below are a microbenchmark of per-response cost, not a claim about a real deployment.

With that caveat, the per-response cost is large and it lands on the paths you least want to be expensive:

  • Every 404 and 429 a real user hits carries ~69 KB of artwork. On mobile or a slow link that is a noticeable page.
  • With the deny path isolated (bans off), response size was the dominant factor in throughput. At a fixed 1,000 blocked req/s the default page produced p50 latency of 9–14 s with 14–28% of requests dropped; shrinking only the body to 146 B (by excluding 403 from interception, everything else identical) gave p50 5.5 ms with zero drops. The audit engine was fully enabled in both cases, so this isolates the page.
  • The duplicated 40 KB is spent on nothing at all.

How to reproduce?

Static check, no runtime needed:

bash
python3 - <<'PY'
import re, hashlib
s = open("src/common/core/errors/files/error.html", encoding="utf-8").read()
svgs = [m.group(0) for m in re.finditer(r"<svg.*?</svg>", s, re.S)]
seen = {}
for x in svgs:
    seen.setdefault(hashlib.md5(x.encode()).hexdigest(), []).append(len(x))
dup = sum(v[0] * (len(v) - 1) for v in seen.values() if len(v) > 1)
print(f"file={len(s):,}  svg={sum(map(len, svgs)):,} ({len(svgs)} blocks)  duplicated={dup:,}")
PY

Rendered size, against any service with ModSecurity blocking:

bash
curl -s -H 'Host: localhost' -o /dev/null -w '%{http_code} %{size_download}\n' \
  "http://127.0.0.1:8080/?id=1%27%20OR%20%271%27=%271"
# 403 69283

Suggested fix

In rough order of effort:

  1. Deduplicate the SVG. Define each repeated block once as a <symbol> inside a single hidden <svg> and reference it with <use href="#id"> wherever it appears. Same rendered result, −40,694 bytes immediately, no design change.
  2. Consider a lighter body for non-browser clients — e.g. when Accept doesn't include text/html, or for status codes that are never meant to be read by a human. A ModSecurity 403 to a scanner or an API client doesn't need the artwork.
  3. Longer term, move the artwork to a static asset served with caching headers, so the error document itself is small and the SVG is fetched once per client rather than inlined in every error response.

I'm happy to send a PR for (1) — it's mechanical and I can keep the rendered output pixel-identical. (2) and (3) are design decisions I'd rather not pre-empt, so I'd follow your lead there.

BunkerWeb version

1.6.9 (measured). error.html and the INTERCEPTED_ERROR_CODES default re-verified unchanged on dev at the time of filing.

What integration are you using?

Docker (all-in-one image).

Notes

Related to #3891, which came out of the same measurement session. I work on WAF engineering commercially; the numbers here are BunkerWeb measured against itself, with no comparative data.