Skip to content

Fix double-escaped ampersands in Markdown image alt and title attributes - #18086

Merged
Princesseuh merged 1 commit into
withastro:mainfrom
ump45nose:fix/18070-image-attr-escape
Sep 21, 2026
Merged

Princesseuh merged 1 commit into
withastro:mainfrom
ump45nose:fix/18070-image-attr-escape

Conversation

@ump45nose

Copy link
Copy Markdown
Contributor

Changes

  • Fix double-escaped ampersands in Markdown image alt and title attributes, e.g. ![A & B](../assets/diagram.svg "C & D") rendered alt="A & B".

Root cause

Markdown images are round-tripped through a __ASTRO_IMAGE_ markup attribute: rehypeImages JSON-serializes the image's properties (including alt/title) into that attribute, and the final <img> is rendered later by decoding it again. The decode step only handled the quote character references the Markdown processors emit — remark writes &#x22;/&#x27;, satteri writes &quot;/&apos; — so an ampersand in the JSON survived the decode as entity text (&amp; or &#x26;) and was escaped a second time when the attribute was finally rendered.

The decode chains in vite-plugin-markdown/images.ts (3 sites — this code is inlined into the browser bundle, so it stays self-contained) and content/runtime.ts (1 site) now decode the ampersand references as well, in both styles, ampersands last: quotes are decoded first so that a literal &quot; in the operator's own alt text is not mistaken for one the serializer wrote, and only then is & un-escaped. Literal entity text in the source is preserved.

Testing

  • Extended the core-image-remark-imgattr fixture with ![A & B](../assets/penguin2.jpg "C & D") and asserted via cheerio that the rendered alt/title are A & B / C & D (cheerio returns the decoded DOM value, so a double escape shows up as a literal &amp;).
  • Verified the test bites: with the ampersand decoding removed the new test fails (actual: 'A &#x26; B'), with it restored it passes.
  • Ran the surrounding suites for regressions: core-image* (215 pass), astro-markdown* (47 pass), units/content-collections/image-references (11 pass), units/content-layer/asset-imports (3 pass). All green.
  • Changeset added: .changeset/fix-image-attr-ampersand.md.

Docs

No docs change needed — the documented behavior of image alt/title is unchanged; this restores it.


Disclosure: implemented with AI assistance (Claude); the root-cause analysis, fix, and verification are mine and I can explain every line of the diff.

The __ASTRO_IMAGE_ round-trip JSON-serializes an image's properties into a markup attribute and decodes them again when the final <img> is rendered. The decode step only handled the quote character references the Markdown processors emit, so an ampersand in an alt or title survived as entity text and was escaped a second time at render: `A & B` came out as `A &amp; B`.

Decode the ampersand references too, in both styles — numeric from remark, named from satteri — ampersands last, so a literal entity in the operator's own text is not mistaken for one the serializer wrote.
@changeset-bot

changeset-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a564573

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
astro Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the pkg: astro Related to the core `astro` package (scope) label Sep 21, 2026
@codspeed

codspeed Bot commented Sep 21, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing ump45nose:fix/18070-image-attr-escape (a564573) with main (cf5d72f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (db2eaf1) during the generation of this report, so cf5d72f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@Princesseuh Princesseuh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thank you!

@Princesseuh
Princesseuh merged commit 795a7e4 into withastro:main Sep 21, 2026
29 checks passed
@astrobot-houston astrobot-houston mentioned this pull request Sep 22, 2026
dadezzz pushed a commit to dadezzz/university_notes that referenced this pull request Sep 27, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [astro](https://astro.build) ([source](https://github.com/withastro/astro/tree/HEAD/packages/astro)) | [`7.3.3` → `7.3.4`](https://renovatebot.com/diffs/npm/astro/7.3.3/7.3.4) | ![age](https://developer.mend.io/api/mc/badges/age/npm/astro/7.3.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/astro/7.3.3/7.3.4?slim=true) |

---

### Release Notes

<details>
<summary>withastro/astro (astro)</summary>

### [`v7.3.4`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#734)

[Compare Source](https://github.com/withastro/astro/compare/astro@7.3.3...astro@7.3.4)

##### Patch Changes

- [#&#8203;18063](withastro/astro#18063) [`40896ac`](withastro/astro@40896ac) Thanks [@&#8203;adamchal](https://github.com/adamchal)! - Fixes incremental builds repeatedly rendering unchanged pages when modules or compiled CSS reference bundled assets.

- [#&#8203;18053](withastro/astro#18053) [`cf5d72f`](withastro/astro@cf5d72f) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Improves the `astro check` error shown for TypeScript 7. The command now explains that TypeScript 7 is not currently supported and provides instructions for experimentally type-checking Astro files with TypeScript 7.1 and `@astrojs/ts-content-mapper`.

- [#&#8203;18086](withastro/astro#18086) [`795a7e4`](withastro/astro@795a7e4) Thanks [@&#8203;ump45nose](https://github.com/ump45nose)! - Fix double-escaped ampersands in Markdown image `alt` and `title` attributes. The `__ASTRO_IMAGE_` round-trip now decodes the numeric (`&#x26;`) and named (`&amp;`) character references the Markdown processors emit, so an `&` in an alt or title is escaped exactly once in the final HTML instead of twice.

- [#&#8203;18074](withastro/astro#18074) [`0429805`](withastro/astro@0429805) Thanks [@&#8203;SurefireStudios](https://github.com/SurefireStudios)! - Fix three error names that did not match their documented reference. `MissingLocale`, `MissingIndexForInternationalization` and `NoManifestAvailable` reported names ending in `Error` in the dev overlay, while their error reference pages are published under the unsuffixed names, so the name shown to users could not be found in the docs.

- [#&#8203;18007](withastro/astro#18007) [`2245837`](withastro/astro@2245837) Thanks [@&#8203;L4XB](https://github.com/L4XB)! - Fixes the dev server re-evaluating the whole server module graph on every request. The `astro:head-metadata` plugin invalidated its component metadata virtual module from its own `transform` hook, so each evaluation of that module scheduled the next one. Adapters that run requests outside Vite's module runner, such as `@astrojs/cloudflare`, paid for a full re-evaluation of the server graph on every request for the lifetime of the process.

- [#&#8203;18096](withastro/astro#18096) [`43657c4`](withastro/astro@43657c4) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Fixes domain-based i18n routing to respect `security.allowedDomains` when selecting a locale from request host headers

- [#&#8203;18043](withastro/astro#18043) [`8a53a8b`](withastro/astro@8a53a8b) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes `image.responsiveStyles` emitting invalid `object-position` CSS values for same-axis keyword pairs (`top bottom`, `left right`, etc.)

- [#&#8203;18029](withastro/astro#18029) [`c08252d`](withastro/astro@c08252d) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Runs `astro dev` and `astro preview` in the foreground when an AI agent is detected on Windows, allowing the agent to manage the process lifetime. Pass `--background` explicitly to request an Astro-managed background process. Agent-inferred backgrounding remains enabled on other platforms.

- Updated dependencies \[[`3fd16ee`](withastro/astro@3fd16ee), [`8358d59`](withastro/astro@8358d59)]:
  - [@&#8203;astrojs/markdown-satteri](https://github.com/astrojs/markdown-satteri)@0.4.2

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTUuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjExNS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: astro Related to the core `astro` package (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants