Skip to content

fix(types): constrain rendered/fetched URLs to http(s) (closes #210)#211

Merged
williamzujkowski merged 1 commit into
mainfrom
fix/constrain-url-schemes
Jun 23, 2026
Merged

fix(types): constrain rendered/fetched URLs to http(s) (closes #210)#211
williamzujkowski merged 1 commit into
mainfrom
fix/constrain-url-schemes

Conversation

@williamzujkowski

Copy link
Copy Markdown
Collaborator

Closes #210 (security hardening from the packages/types review).

Problem

z.url() accepts dangerous schemes (javascript:, data:, ftp:). Two fields validated with it reach sensitive sinks:

  • CaseAnnotationSchema.sourceUrl is rendered directly as an hrefPrecedentDrawer.svelte:93,145, statute/[...slug].astro:397 → a javascript: value is XSS-on-click.
  • ReleasePointSchema.uslmUrl is used to download content in the fetcher.

The web content collection was even looser (sourceUrl: z.string()).

Fix

  • New exported HttpUrlSchema = z.url().refine(http(s)) in packages/types, applied to uslmUrl and sourceUrl.
  • apps/web/src/content.config.ts: sourceUrl tightened to http(s)-or-empty so malformed annotation data fails the content build loudly instead of rendering a bad href.

Low real-world likelihood today (URLs are constructed from uscode.house.gov / courtlistener.com), but the scheme was unenforced at the schema/render boundary — this closes it as defense-in-depth alongside #200/#207.

Verification

  • Confirmed z.url() accepts javascript: in this Zod version; the refined schema rejects javascript:/ftp:/garbage and accepts https.
  • New tests reject javascript:/ftp: for uslmUrl and javascript: for sourceUrl.
  • pnpm --filter @civic-source/types test (40), fetcher (123), annotator all pass; pnpm build 8/8. No fixture breakage (production URLs are https).

🤖 Generated with Claude Code

z.url() accepts dangerous schemes (javascript:, data:, ftp:). uslmUrl is
used to download content and sourceUrl is rendered directly as an href
in the precedent UI (PrecedentDrawer.svelte, statute page), so a
malformed/compromised value could yield a clickable javascript: link
(XSS on click) or an off-protocol download.

Add a shared HttpUrlSchema (z.url().refine(http(s))) and apply it to
ReleasePointSchema.uslmUrl and CaseAnnotationSchema.sourceUrl. Tighten
the web content collection's sourceUrl (was z.string()) to the same
http(s)-or-empty constraint so bad data fails the content build loudly.

Low real-world likelihood today (URLs are constructed from
uscode.house.gov / courtlistener.com), but the scheme was previously
unenforced at the schema/render boundary. New tests assert javascript:
and ftp: are rejected for both fields.

Closes #210

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@williamzujkowski williamzujkowski requested a review from a team as a code owner June 23, 2026 04:48
@williamzujkowski williamzujkowski merged commit 63d4375 into main Jun 23, 2026
3 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.

security: constrain rendered URLs to http(s) — z.url() accepts javascript: for sourceUrl/uslmUrl

1 participant