Skip to content

Allow data: fonts in the standalone HTML CSP#285

Merged
Alek99 merged 1 commit into
mainfrom
alek/fix-csp-font-src
Jul 25, 2026
Merged

Allow data: fonts in the standalone HTML CSP#285
Alek99 merged 1 commit into
mainfrom
alek/fix-csp-font-src

Conversation

@Alek99

@Alek99 Alek99 commented Jul 25, 2026

Copy link
Copy Markdown
Member

Problem

_STANDALONE_CSP declares no font-src, so font loads fall back to
default-src 'none' and every face is blocked — including the data-URL
@font-face that docs/styling/themes-and-tokens.md documents as the portable brand-font recipe.

A standalone export silently rendered in system-ui, leaving only a console CSP
violation to explain it. Because to_image(engine=Engine.chromium) renders that
same document, the failure reached Chromium PNG too — the path the docs point at
precisely when font fidelity matters.

Captured live in a browser against a generated export:

securitypolicyviolation → { violatedDirective: "font-src", blockedURI: "data" }
document.fonts.check('12px "XY Audit Face"') → false
computed font-family → "system-ui, sans-serif"

Before / after

Same script, same embedded face (Chalkduster as a data: URL under a custom
family name, so it can only come from the data URL — no local fallback can
satisfy it). Left: origin/main. Right: this branch.

Before After
before after

After the fix: zero CSP violations, document.fonts.check(...)true.

Fix

Add font-src data: beside the existing img-src data:. Exactly data: — no
network origin can serve a face, so a standalone file stays self-contained
offline, which is the property default-src 'none' exists to protect.

The recipe had a second blocker, also fixed here in docs: the chart root
carries a computed font shorthand that an ordinary .xy { font-family: ... }
rule cannot outrank. The docs now use xy.theme(font_family=...), which writes
that shorthand — verified in the screenshot above.

Notes

  • The CSP string is duplicated in scripts/render_smoke_nonumpy.py and asserted
    equal by an existing test, so both move together.
  • The new test asserts font-src is exactly data:. Existing coverage only
    checked the worker-src substring and self-consistency, which is why this
    went unnoticed.
  • spec/process/security-audit-2026-07-06.md gets a status entry, matching how
    the worker-src relaxation was recorded.

Found during a customizability audit of the styling surface.

`_STANDALONE_CSP` declared no `font-src`, so font loads fell back to
`default-src 'none'` and every face was blocked — including the data-URL
`@font-face` that docs/styling/themes-and-tokens.md documents as the portable
brand-font recipe. A standalone export silently rendered in system-ui, leaving
only a console CSP violation, and the same failure reached Chromium PNG because
it renders that document.

Add `font-src data:` — exactly `data:`, matching the existing `img-src data:`
shape, so no network origin can serve a face and a standalone file stays
self-contained offline.

The documented recipe had a second blocker: the chart root carries a computed
`font` shorthand that an ordinary `.xy { font-family: ... }` rule cannot
outrank. Point the docs at `xy.theme(font_family=...)`, which writes that
shorthand.

The CSP is duplicated in scripts/render_smoke_nonumpy.py and asserted equal by
the existing test, so update both. The new test asserts `font-src` is exactly
`data:` — the old coverage only checked the `worker-src` substring, which is
why this went unnoticed.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Alek99, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ec170a5-9e80-4dea-b0d0-fa9a75e0ea3e

📥 Commits

Reviewing files that changed from the base of the PR and between b7d9694 and 16c662e.

📒 Files selected for processing (6)
  • docs/guides/serving-csp-and-offline-use.md
  • docs/styling/themes-and-tokens.md
  • python/xy/export.py
  • scripts/render_smoke_nonumpy.py
  • spec/process/security-audit-2026-07-06.md
  • tests/test_static_client_security.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/fix-csp-font-src

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing alek/fix-csp-font-src (16c662e) with main (b7d9694)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Alek99
Alek99 merged commit 7d5af0c into main Jul 25, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant