fix(security): 属性级转义防注入 + 发布产物扩展名修复

- 链接/图片 href/src/alt/title 与代码块 language 属性改用属性级转义
  (浏览器环境 escapeHTML 不转义双引号,存在属性注入面)
- 修复 title 中反斜杠转义还原,引用链接 title 输出与行内一致
- 产物改用 .mjs/.cjs 扩展名(type:module 下 .js 被 Node 按 ESM 解析,
  require() 拿不到导出);exports 指向 dist 而非 src TS 源码
- 新增 .gitattributes 强制 LF,避免 Windows CRLF 污染 diff
- 配套测试:title/URL/alt/language 注入防护
This commit is contained in:
2026-08-09 08:59:19 +08:00
parent 9fe209ba88
commit ddc650feeb
15 changed files with 115 additions and 14278 deletions
+58
View File
@@ -1270,3 +1270,61 @@ describe('parseMarkdown - v0.2.2 自定义 token 渲染', () => {
expect(html).toContain('<strong>Important!</strong>');
});
});
// ============ v0.2.5 链接 title 属性转义 ============
describe('parseMarkdown - v0.2.5 title 转义', () => {
test('链接 title 中的引号被转义', () => {
const html = parseMarkdown('[text](https://example.com "say \\"hi\\"")');
expect(html).toContain('title="say &quot;hi&quot;"');
expect(html).not.toContain('title="say "hi""');
});
test('链接 title 中的尖括号被转义', () => {
const html = parseMarkdown('[text](https://example.com "a<b>c")');
expect(html).toContain('title="a&lt;b&gt;c"');
});
test('图片 title 被转义', () => {
const html = parseMarkdown('![alt](https://example.com/i.png "x\\"y")');
expect(html).toContain('title="x&quot;y"');
});
test('引用链接 title 被转义', () => {
const html = parseMarkdown('[ref]: https://example.com "a\\"b"\n\n[text][ref]');
expect(html).toContain('title="a&quot;b"');
});
test('引用图片 title 被转义', () => {
const html = parseMarkdown('[img]: https://example.com/i.png "c<d>"\n\n![alt][img]');
expect(html).toContain('title="c&lt;d&gt;"');
});
test('链接 URL 中的引号不能注入属性', () => {
const html = parseMarkdown('[x](https://example.com/"onclick="alert(1))');
expect(html).not.toContain(' onclick=');
expect(html).toContain('&quot;onclick');
});
test('图片 alt 中的引号不能注入属性', () => {
const html = parseMarkdown('![a" onerror="alert(1)](https://example.com/i.png)');
expect(html).not.toContain(' onerror="');
expect(html).toContain('&quot;');
});
test('代码块语言属性中的引号不能注入', () => {
const html = parseMarkdown('```js" onclick="x\ncode\n```');
expect(html).not.toContain('language-js');
expect(html).not.toContain('onclick="x"');
});
test('代码块 title 属性中的引号不能注入', () => {
const html = parseMarkdown('```js title="a" onload="x"\ncode\n```');
expect(html).not.toContain('onload');
});
test('链接 title 中的反斜杠转义被还原', () => {
const html = parseMarkdown('[text](https://example.com "say \\"hi\\"")');
expect(html).toContain('title="say &quot;hi&quot;"');
});
});