(Bug) Self-hosted deployments still have no working TSA preset after #869 (all presets fail CORS preflight)
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:
$ 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:
$ 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):
$ 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: sha256Adding 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.comAny 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
- Deploy
ghcr.io/alam00000/bentopdfself-hosted (noVITE_CORS_PROXY_URL). - Open
/timestamp-pdf, load any PDF, pick any TSA, Apply Timestamp. - 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
Source: alam00000/bentopdf