diff --git a/src/features/tech-log/domain/content-format/parse-case-content.ts b/src/features/tech-log/domain/content-format/parse-case-content.ts index 578cefe..1a1a133 100644 --- a/src/features/tech-log/domain/content-format/parse-case-content.ts +++ b/src/features/tech-log/domain/content-format/parse-case-content.ts @@ -199,6 +199,7 @@ function isSafeLink(href: string): boolean { return false; } if (href.startsWith("#")) return true; + if (href.startsWith("//")) return false; if (href.startsWith("/")) { try { return ( diff --git a/tests/features/tech-log/content-format.test.ts b/tests/features/tech-log/content-format.test.ts index 085c80d..934f6a7 100644 --- a/tests/features/tech-log/content-format.test.ts +++ b/tests/features/tech-log/content-format.test.ts @@ -39,6 +39,9 @@ describe("Content Format v1", () => { "", "[x](javascript:alert(1))", "[x](//evil.example/path)", + "[x](//techlog.invalid/path)", + "[x](///techlog.invalid/path)", + "[x](//user@techlog.invalid/path)", "[x](/\\evil.example/path)", "[x](/\\\\evil.example/path)", "[x]()", @@ -75,7 +78,7 @@ describe("Content Format v1", () => { it("keeps explicitly allowed links and same-origin paths", () => { const [paragraph] = parseCaseContent( - "[fragment](#section) [path](/safe/path?q=one) [http](http://example.com/path) [https](https://example.com/path) [mail](mailto:test@example.com)", + "[fragment](#section) [path](/safe/path) [query](/safe/path?q=one) [http](http://example.com/path) [https](https://example.com/path) [mail](mailto:test@example.com)", ); expect(paragraph?.type).toBe("PARAGRAPH"); @@ -86,6 +89,7 @@ describe("Content Format v1", () => { .map((inline) => inline.href), ).toEqual([ "#section", + "/safe/path", "/safe/path?q=one", "http://example.com/path", "https://example.com/path",