Skip to content

fix(security): sanitize untrusted calendar text before rendering (audit bucket 5 — M1) - #6

Merged
CR0CKER merged 1 commit into
mainfrom
fix/sanitize-block-fields
Jul 20, 2026
Merged

CR0CKER merged 1 commit into
mainfrom
fix/sanitize-block-fields

Conversation

@CR0CKER

@CR0CKER CR0CKER commented Jul 20, 2026

Copy link
Copy Markdown
Owner

What changed

Audit bucket 5, finding M1. Event summary, description, location, and attendee display names come from an external (possibly hostile or compromised) iCal feed and were rendered into Logseq blocks verbatim — with attendee names wrapped in [[...]]. A crafted value could inject page refs, block refs, or {{macro}}/{{query}}/{{renderer}} into the user's graph.

  • New sanitizeForBlock (parsing.ts): inserts a zero-width space inside each Logseq control token ([[ ]] {{ }} (( ))) so it can't be parsed as markup, while the visible text is unchanged.
  • Applied to the attendee name/email in formatParticipants (inside the plugin's own [[ ]] wrapper, which still links correctly to a sanitized name) and to summary / description / location at the render site in insertJournalBlocks.

Why

Stored-injection from a third party: anyone who can place an entry on your calendar (a meeting invite, a shared calendar) controls these fields. Rendering them as live Logseq markup lets them create pages, run queries, or embed renderers in your notes.

Trade-off (intentional)

A calendar field that legitimately contains [[...]] will no longer auto-link. That's the correct posture for untrusted external input — the plugin's own intentional links (participant names) still work because the wrapper is added around sanitized content.

Testing

  • npm test: 42 pass (5 new), including a hostile-attendee-name case (evil]] {{query ...}}) asserting no parseable control token survives inside the link.
  • npm run typecheck: 0 (blocking gate stays green).
  • Build verified via CI.

Reviewer note

No automated PR reviewer configured; self-reviewed — sanitization applied at all four untrusted render paths, behavior preserved for the undefined-field case, no secrets.

🤖 Generated with Claude Code

https://claude.ai/code/session_019cVCdCtNzUJmy1yen3JyBk

… (audit M1)

Event summary, description, location, and attendee names come from an external
iCal feed. An event the user did not author (a meeting invite, a shared or
subscribed calendar) could carry a {{query}}/{{renderer}} in its title or
description and have it execute in the user's graph when rendered.

Add sanitizeForBlock (parsing.ts): inserts a zero-width space inside each
{{ / }} token so it can't be parsed as a macro, visible text unchanged. Applied
to attendee name/email in formatParticipants and to summary/description/location
in insertJournalBlocks.

Scoped to macros only by user preference: page refs [[...]], block refs ((...)),
and #tags are left intact — they are inert and users legitimately put them in
their own event titles/descriptions and want them to link.

Tests: 43 pass; typecheck 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019cVCdCtNzUJmy1yen3JyBk
@CR0CKER
CR0CKER force-pushed the fix/sanitize-block-fields branch from 36a52ca to 35a23ac Compare July 20, 2026 18:21
@CR0CKER
CR0CKER merged commit 0e53061 into main Jul 20, 2026
3 of 4 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