Fix XSS in Markdown renderer + packaging hygiene#2
Open
patricktr wants to merge 3 commits into
Open
Conversation
The Markdown component rendered untrusted cell content via
dangerouslySetInnerHTML with two compounding flaws:
1. escapeHtml did not escape ", but captured groups were
interpolated into href="...". Payload [x](https://e" onclick="x")
produced a tag with an injected onclick attribute.
2. URL schemes in links were not validated, so [click](javascript:...)
produced an executable link.
Either flaw lets a base collaborator who can write to a long-text
field execute JS in another user's extension iframe and call
updateRecordAsync impersonating the victim.
Fix:
- Replace dangerouslySetInnerHTML with structured React-node
rendering so React handles attribute escaping.
- Allowlist http:/https:/mailto: for both explicit links and
autolinks; non-matching schemes render as plain text.
- Move useMemo above the early returns to fix a rules-of-hooks
violation that crashed on empty-to-content transitions.
Also harden AttachmentPreview to allow only http:/https: image URLs
and set referrerPolicy="no-referrer" so embedded images can't leak
referer to attacker-controlled origins.
Document the trust model in the README so consumers know what the
toolkit handles vs. what they must handle themselves.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The published package was broken: main and exports referenced ./src/... but the source lives in ./frontend/. Fix the paths and add the missing publish-readiness fields: - files allowlist so npm publish doesn't ship dotfiles or examples - sideEffects: false for tree-shaking - type: module (the source uses ESM) - engines.node and publishConfig.access - React peer upper bound (>=17 <20) - repository.url updated to git+https form Also fix stale path references in the README (src/ -> frontend/, skill/SKILL.md -> SKILL.md) so the install instructions match what's actually on disk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous URL referenced victoriaplummer/airtable-extension-toolkit, which doesn't exist — the actual repo name is airtable-interface-extension-toolkit (with -interface-). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two compounding XSS issues in
<Markdown>plus a few packaging fixes that prevent the published toolkit from working.Security fixes (commit 6afabcb)
frontend/components/Markdown.jsrendered untrusted cell content viadangerouslySetInnerHTMLwith two compounding flaws:escapeHtmldid not escape\", but captured groups were interpolated intohref=\"...\". Payload[x](https://e\" onclick=\"alert(1))produced<a href=\"https://e\" onclick=\"alert(1)\" ...>.[click](javascript:alert(1))produced an executable link;data:text/html,...similarly.Either lets a base collaborator who can write to a long-text field (or AI output) execute JS in another user's extension iframe and call
updateRecordAsyncimpersonating the victim.The fix replaces
dangerouslySetInnerHTMLwith structured React-node rendering (so React handles attribute escaping) and allowlistshttp:/https:/mailto:for both explicit and autolinks. Non-matching schemes render as plain text.Also fixed a rules-of-hooks violation in the same file (
useMemocalled after early returns) that crashed the component on empty-to-content transitions, and hardened<AttachmentPreview>to allow onlyhttp:/https:image URLs withreferrerPolicy=\"no-referrer\".A new "Security model" section in the README documents the trust boundary and what consumers are responsible for (permission gating before
updateRecordAsync, treating AI output as adversarial).Packaging fixes (commits 32e6dd7, 68f2745)
The published package was broken:
mainandexportsreferenced./src/...but the source lives in./frontend/. Also:filesallowlist sonpm publishdoesn't ship dotfiles or examplessideEffects: falsefor tree-shakingtype: module(the source uses ESM)engines.nodeandpublishConfig.access>=17 <20) — the original>=17was unboundedrepository.url— the previous value pointed atvictoriaplummer/airtable-extension-toolkit(without-interface-), which doesn't existsrc/→frontend/,skill/SKILL.md→SKILL.md)Test plan
<Markdown>renders the XSS PoCs as plain text / safe links:[click](javascript:alert(1))→ renders as plain text "click"[x](https://e\" onclick=\"alert(1))→ if the URL parses, the\"is React-escaped inside thehrefattributehttps://e\"onerror=\"x→ autolink path same as above<Markdown>still renders normal markdown correctly (headings, lists, code, blockquotes, bold/italic, real links).useMemo-after-early-return bug).import { Markdown } from 'airtable-extension-toolkit'and the subpath exports work.Disclosure note
I considered filing a private security advisory but private vulnerability reporting is currently disabled on this repo. Happy to re-do this as a private advisory if you'd like to enable it (Settings → Code security → "Privately report a vulnerability") — let me know and I'll close this and re-file. Otherwise, since the fix is in the same PR, public disclosure here gives downstream consumers an immediate patch path.
🤖 Generated with Claude Code