fix: escape passthrough tags in rendered attributes#222
Merged
Conversation
Whitelisted inline tags (br|u|sub|sup|mark|details|summary) are stashed as placeholders before markdown rendering and restored across the whole HTML string. When a placeholder rendered into a link/image title attribute, the raw tag was restored verbatim, injecting unescaped <>"& into the attribute value and producing storage XML that Confluence rejects. Detect the attribute context at restore time (nearest preceding angle bracket opens a tag) and XML-escape the restored value there. Body-text placeholders are left raw, so passthrough output stays byte-identical. Covers all seven whitelisted tags. Adds regression tests for link title <br>, image title <sub>, a mixed body+title case, and a well-formed XML assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 23, 2026
## [2.19.1](v2.19.0...v2.19.1) (2026-07-23) ### Bug Fixes * escape passthrough tags in rendered attributes ([#222](#222)) ([71f1b31](71f1b31))
|
🎉 This PR is included in version 2.19.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.
Intent
Fix the passthrough-tag attribute-escaping defect in confluence-cli's markdown->storage converter (roadmap #2, conf-passthrough-attresc-p2). lib/macro-converter.js stashes allow-listed inline tags (br|u|sub|sup|mark|details|summary) as placeholders before markdown rendering and restores them globally across the whole rendered HTML string. When a placeholder rendered into a link/image title attribute, the raw tag was restored verbatim, injecting unescaped <>"& into an attribute value and producing storage XML that Confluence rejects. The captain approved a SINGLE root-fix scope (S), NOT a broad pipeline sweep: fix only this defect, covering all seven allow-listed tags at once, and keep body-text passthrough byte-identical. Approach: at restore time detect the attribute context (nearest preceding angle bracket opens a tag: last '<' after last '>' before the placeholder offset) and XML-escape the restored value via a new escapeXmlAttr helper (& first, then < > "); text-node placeholders stay raw so body output is unchanged. Added 5 regression tests (link title
, image title , mixed body+title, all-seven-tags, well-formed-XML assertion). Deliberately did NOT expand into other pipeline areas (#210/#213/#215, admonitions, link rewriting) - that broader sweep was explicitly out of scope. Full suite 851 pass, lint clean. fix: type for patch release via semantic-release.
What Changed
Risk Assessment
✅ Low: The follow-up removes the quadratic scan while preserving the required attribute-context detection and unchanged body passthrough behavior.
Testing
Targeted regressions, all 851 repository tests, actual CLI conversion, strict XML parsing, and base-versus-target body parity passed; evidence shows all-seven title escaping, embedded quote/ampersand escaping, and byte-identical body passthrough.
Evidence: Generated Confluence storage XML
Evidence: Base/target body parity
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
lib/macro-converter.js:155- Each placeholder rescans the entire preceding HTML, making restoration O(n²) when passthrough-tag count scales with document size. Track tag context with a forward cursor so each character is scanned once.🔧 Fix: Optimize passthrough placeholder context scanning
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Reviewedgit diff 088011cd02fb454023b156f601697471ad1b0907..b869b1ba9dc75f07904163d90a82bba97e9670a7npx jest tests/macro-converter.test.js --runInBand --testNamePattern='passthrough tags in link/image title attributes|<br> passthrough|<u>/<sub>/<sup>/<mark> passthrough'Piped mixed body/title Markdown throughnode bin/confluence.js convert --input-format markdown --output-format storage --output-file /var/folders/mm/mzvsb9xn40vd3pqkp4y8n_sm0000gn/T/no-mistakes-evidence/01KY7PSSSVT3P34GFPFAW2911A/passthrough-attribute-escaping.storage.xmlWrapped the generated fragment in<root>and ranxmllint --noout -Compared all-seven body output from the base and target converters byte-for-byte and by SHA-256npm test -- --runInBand✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.