Skip to content

Commit 0e9c611

Browse files
committed
Sanitize dangerous JSDoc sequences to avoid prematurely closing comments
1 parent fa2a088 commit 0e9c611

File tree

3 files changed

+94
-12
lines changed

3 files changed

+94
-12
lines changed

src/schema-parser/schema-formatters.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,33 @@ export class SchemaFormatters {
105105
formatDescription = (description, inline) => {
106106
if (!description) return "";
107107

108-
const hasMultipleLines = description.includes("\n");
108+
// Sanitize dangerous JSDoc sequences to avoid prematurely closing comments
109+
// e.g. "**/information**" contains "*/" which ends a JSDoc block
110+
const sanitizeForJsDoc = (text: string) =>
111+
text
112+
// Break the JSDoc terminator so it doesn't end the comment block
113+
.replace(/\*\//g, "*\\/");
109114

110-
if (!hasMultipleLines) return description;
115+
const safeDescription = sanitizeForJsDoc(String(description));
116+
117+
const hasMultipleLines = safeDescription.includes("\n");
118+
119+
if (!hasMultipleLines) return safeDescription;
111120

112121
if (inline) {
113-
return lodash
114-
.chain(
115-
description
116-
.replace(/\//g, "")
117-
.split(/\n/g)
118-
.map((part) => part.trim()),
119-
)
120-
.compact()
121-
.value();
122+
return (
123+
lodash
124+
// @ts-expect-error TS(2339) FIXME: Property '_' does not exist on type 'LoDashStatic'... Remove this comment to see the full error message
125+
._(safeDescription)
126+
.split(/\n/g)
127+
.map((part: string) => part.trim())
128+
.compact()
129+
.join(" ")
130+
.valueOf()
131+
);
122132
}
123133

124-
return description.replace(/\n$/g, "");
134+
return safeDescription.replace(/\n$/g, "");
125135
};
126136

127137
formatObjectContent = (content) => {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as fs from "node:fs/promises";
2+
import * as os from "node:os";
3+
import * as path from "node:path";
4+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
5+
import { generateApi } from "../../../src/index.js";
6+
7+
describe("descriptionSanitize", async () => {
8+
let tmpdir = "";
9+
10+
beforeAll(async () => {
11+
tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "swagger-typescript-api"));
12+
});
13+
14+
afterAll(async () => {
15+
await fs.rm(tmpdir, { recursive: true });
16+
});
17+
18+
test("route description with JSDoc terminator is escaped", async () => {
19+
await generateApi({
20+
// Use defaults to generate single Api.ts with client and docs
21+
input: path.resolve(import.meta.dirname, "schema.yml"),
22+
output: tmpdir,
23+
silent: true,
24+
});
25+
26+
const content = await fs.readFile(path.join(tmpdir, "Api.ts"), {
27+
encoding: "utf8",
28+
});
29+
30+
// Ensure the potentially dangerous sequence is escaped inside JSDoc
31+
expect(content).toContain(
32+
"@description Get service point file of all Nordic countries (SE,FI,DK,NO) from S3 storage.",
33+
);
34+
expect(content).toContain("**\\/information** endpoint");
35+
36+
// And ensure the raw terminator never appears inside the block
37+
expect(content).not.toMatch(/\*\*\//); // "**/" shouldn't be present
38+
});
39+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
openapi: 3.0.3
2+
info:
3+
title: desc-escape
4+
version: 0.0.0
5+
paths:
6+
/v5/servicepoints/file/ALL-SE-FI-DK-NO-{date}.json.gz:
7+
get:
8+
tags:
9+
- Service Points File
10+
summary: Get service point file of all Nordic countries (SE,FI,DK,NO).
11+
description: |-
12+
Get service point file of all Nordic countries (SE,FI,DK,NO) from S3 storage. You can download previous service point file upto 7 days from current date. This is equivalent to **/information** endpoint with parameters `countryCode:SE,FI,DK,NO` and `context:ALL` and header `Accept-Encoding:gzip`.
13+
14+
Download the file using the URL in reponse.
15+
operationId: ServicePointFile
16+
parameters:
17+
- name: date
18+
in: path
19+
description: Date of file you want to download. Format should be **YYYY-MM-DD**.
20+
required: true
21+
style: simple
22+
explode: false
23+
schema:
24+
type: string
25+
responses:
26+
'200':
27+
description: OK
28+
content:
29+
application/gzip:
30+
schema:
31+
type: string
32+
'401':
33+
description: Authentication token is missing/invalid

0 commit comments

Comments
 (0)