feat(markdown): 외부 링크 새 탭 + rel=noopener noreferrer (P0)

docMarked link 렌더러: http/https 링크에 target=_blank rel=noopener noreferrer
(탭내빙 차단, 코퍼스 521건). 내부/'#'프래그먼트/상대/mailto 는 무손 — outline
gfmHeadingId 경로 유지(클릭 인터셉터 없음=충돌 0). marked15 토큰객체 시그니처.
SANITIZE_OPTS ADD_ATTR 에 target/rel.

load-bearing 게이트: 상대 .md=코퍼스 0건·doc_key 부재 → path→id prop/document_links
미구현(dead). [[..]]=13건 대부분 인용 노이즈([[3\]]) → resolution/스트립 미구현.
외부 링크 하드닝만 정당화됨.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyungi
2026-06-15 15:06:58 +09:00
parent a6d5734f6c
commit fabbca64e9
+15
View File
@@ -65,6 +65,19 @@ docMarked.use({
`</figure>` `</figure>`
); );
}, },
// 외부 링크(http/https) → 새 탭 + rel=noopener noreferrer (탭내빙 차단). 521건 실재.
// 내부/프래그먼트/상대 링크는 손대지 않음 — `#` anchor 는 gfmHeadingId/outline 경로 유지
// (클릭 인터셉터 없음 → 충돌 0), 상대 .md(코퍼스 0건)는 기본 동작(inert). marked 15 토큰객체 시그니처.
link(token: any): string {
const href = (token?.href ?? '') as string;
const text = this.parser.parseInline(token?.tokens ?? []);
const titleAttr = token?.title ? ` title="${escAttr(token.title as string)}"` : '';
const safeHref = escAttr(href);
if (/^https?:\/\//i.test(href)) {
return `<a href="${safeHref}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
}
return `<a href="${safeHref}"${titleAttr}>${text}</a>`;
},
}, },
}); });
@@ -82,6 +95,8 @@ const SANITIZE_OPTS = {
'data-md-image-internal', 'data-md-image-internal',
'data-md-image-alt', 'data-md-image-alt',
'loading', 'loading',
'target',
'rel',
], ],
ADD_TAGS: ['figure', 'figcaption'], ADD_TAGS: ['figure', 'figcaption'],
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'link', 'meta'], FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'link', 'meta'],