Skip to content

fix: escape passthrough tags in rendered attributes#222

Merged
pchuri merged 2 commits into
mainfrom
fm/conf-passthrough-attresc-p2
Jul 23, 2026
Merged

fix: escape passthrough tags in rendered attributes#222
pchuri merged 2 commits into
mainfrom
fm/conf-passthrough-attresc-p2

Conversation

@pchuri

@pchuri pchuri commented Jul 23, 2026

Copy link
Copy Markdown
Owner

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

  • Escape restored passthrough tags inside link and image title attributes while preserving raw tags in body text.
  • Track tag context with a forward cursor for linear-time placeholder restoration.
  • Add regression coverage for mixed contexts, all seven allowed tags, and well-formed XML output.

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
<p>Body H<sub>2</sub>O, <u>under</u>, <mark>highlight</mark>, x<sup>2</sup>, line<br />break.</p>
<p><a href="https://example.com" title="before&lt;br&gt;&lt;u&gt;&lt;sub&gt;&lt;sup&gt;&lt;mark&gt;&lt;details&gt;&lt;summary&gt;after">all seven</a></p>
<p><a href="https://example.com" title="&lt;mark data-x=&quot;A&amp;B&quot;&gt;">attribute chars</a></p>
Evidence: Base/target body parity
body_passthrough_byte_identical=true
base_bytes=247 target_bytes=247
base_sha256=57bf12897bcd913262d29f79fbfde7b0b026e51e3e2885c3995073894170bd5d
target_sha256=57bf12897bcd913262d29f79fbfde7b0b026e51e3e2885c3995073894170bd5d

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.

  • Reviewed git diff 088011cd02fb454023b156f601697471ad1b0907..b869b1ba9dc75f07904163d90a82bba97e9670a7
  • npx 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 through node 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.xml
  • Wrapped the generated fragment in &lt;root&gt; and ran xmllint --noout -
  • Compared all-seven body output from the base and target converters byte-for-byte and by SHA-256
  • npm test -- --runInBand
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

pchuri and others added 2 commits July 23, 2026 23:40
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>
@pchuri
pchuri merged commit 71f1b31 into main Jul 23, 2026
6 checks passed
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))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.19.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant