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:
@@ -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'],
|
||||||
|
|||||||
Reference in New Issue
Block a user