#874·bentopdf

(Bug) Self-hosted deployments still have no working TSA preset after #869 (all presets fail CORS preflight)

Author: elkojoCreated Sep 15, 2026Updated Sep 15, 2026

Description

Following #777 and PR #869: #869 fixes the official domains by falling back to the project CORS proxy when VITE_CORS_PROXY_URL is unset. Its description is explicit that this does not help self-hosters:

Self-hosters are unaffected: they still need their own proxy via VITE_CORS_PROXY_URL.

So once #869 lands, https://www.bentopdf.com works, but every self-hosted deployment still has zero working TSA presets out of the box — because none of the five built-in endpoints send Access-Control-Allow-Origin, and the project worker returns 403 to non-official origins:

bash
$ curl -s -o /dev/null -w '%{http_code}\n' \
    -H 'Content-Type: application/timestamp-query' --data-binary @req.tsq \
    'https://bentopdf-cors-proxy.bentopdf.workers.dev/?url=https%3A%2F%2Ffreetsa.org%2Ftsr'
403
{"error":"Forbidden","message":"This proxy only accepts requests from allowed origins"}

Requiring every self-hoster to deploy a Cloudflare Worker is a steep barrier for what is otherwise a zero-backend tool. I'd like to suggest a way to avoid it.

Preflight status of the current presets

The RFC 3161 POST uses Content-Type: application/timestamp-query, which is not a CORS-simple content type, so a preflight is always sent:

bash
$ curl -s -i -X OPTIONS "$u" -H 'Origin: https://pdf.example.org' \
    -H 'Access-Control-Request-Method: POST' \
    -H 'Access-Control-Request-Headers: content-type'
Preset OPTIONS Access-Control-Allow-Origin
https://freetsa.org/tsr 403 none
http://timestamp.digicert.com 501 none
http://timestamp.sectigo.com 200 none
http://ts.ssl.com 400 none
http://tsa.mesign.com 405 none
https://rfc3161.ai.moda 200 echoes the request origin

This also means #867 (moving FreeTSA to the front) will not fix unproxied deployments on its own — FreeTSA is HTTPS, so it clears the mixed-content guard, but its preflight still 403s.

Suggestion 1 — ship one CORS-capable preset

https://rfc3161.ai.moda is a public RFC 3161 responder that sends full CORS headers and returns valid tokens (I've seen it answer under both the DigiCert and Sectigo policy arcs, so it appears to front established CA backends):

bash
$ openssl ts -query -data file.txt -sha256 -cert -out req.tsq
$ curl -s -o resp.tsr -H "Content-Type: application/timestamp-query" \
       --data-binary @req.tsq https://rfc3161.ai.moda
$ openssl ts -reply -in resp.tsr -text | head -5
Status info:
Status: Granted.
Status description: unspecified
Policy OID: 2.16.840.1.114412.7.1
Hash Algorithm: sha256

Adding it as a preset would make the tool work on self-hosted instances with no proxy infrastructure at all. I have no affiliation with the operator — it is simply the only public TSA I could find that is usable from a browser, so its trust/longevity is worth your own evaluation before adopting.

Suggestion 2 — the CSP has to allow the TSA origin too

Worth flagging because a preset change alone is not sufficient. The shipped /etc/nginx/security-headers.conf pins connect-src to the proxy host:

connect-src 'self' blob: https://api.github.com https://fonts.gstatic.com
  https://cdn.jsdelivr.net https://bentopdf-cors-proxy.bentopdf.workers.dev
  https://rawcdn.githack.com

Any TSA contacted directly (rather than through the proxy) must be in connect-src, or the request fails with an identical-looking TypeError: Failed to fetch before it reaches the network. Verified on a self-hosted instance: a fetch() to any TSA origin rejects in ~1 ms with the CSP violation. Since the docs say script-src/connect-src/font-src are generated at build time from the VITE_* URLs, the TSA origins presumably need to feed that generator too.

Steps to Reproduce

  1. Deploy ghcr.io/alam00000/bentopdf self-hosted (no VITE_CORS_PROXY_URL).
  2. Open /timestamp-pdf, load any PDF, pick any TSA, Apply Timestamp.
  3. Every preset fails — the HTTP ones via the mixed-content guard on an HTTPS page, and all five via CSP/CORS otherwise.

Environment

  • BentoPDF v2.8.8, ghcr.io/alam00000/bentopdf (arm64), via the Umbrel app store
  • Served over plain HTTP on a LAN address
  • Chrome