From 5d3acec56713e2c6544adbfd22a9e4e9061264f6 Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:55:40 +0530 Subject: [PATCH 1/7] refactor the imports --- scripts/fetch-asyncapi-example.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index a354b9d6..4900da04 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -1,15 +1,15 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const fs = require('fs'); +const fs = require('node:fs'); const unzipper = require('unzipper'); -const path = require('path'); +const path = require('node:path'); const { Parser } = require('@asyncapi/parser/cjs'); const { AvroSchemaParser } = require('@asyncapi/avro-schema-parser'); const { OpenAPISchemaParser } = require('@asyncapi/openapi-schema-parser'); const { RamlDTSchemaParser } = require('@asyncapi/raml-dt-schema-parser'); -const { pipeline } = require('stream'); -const { promisify } = require('util'); +const { pipeline } = require('node:stream'); +const { promisify } = require('node:util'); const streamPipeline = promisify(pipeline); From 0dfd3d60e3f7b7e13b1667c0ebeb608660681665 Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:58:51 +0530 Subject: [PATCH 2/7] refactor: replace async IIFE with top-level await --- scripts/fetch-asyncapi-example.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index 4900da04..4dc0d328 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -118,9 +118,8 @@ const tidyUp = async () => { fs.unlinkSync(TEMP_ZIP_NAME); }; -(async () => { - await fetchAsyncAPIExamplesFromExternalURL(); - await unzipAsyncAPIExamples(); - await buildCLIListFromExamples(); - await tidyUp(); -})(); + +await fetchAsyncAPIExamplesFromExternalURL(); +await unzipAsyncAPIExamples(); +await buildCLIListFromExamples(); +await tidyUp(); From b9eea3dc7ddc51db6bc9382f8e9477998d3956c3 Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:16:56 +0530 Subject: [PATCH 3/7] refactor: add condition to the tidyUp function --- scripts/fetch-asyncapi-example.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index 4dc0d328..61426dc7 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -114,8 +114,14 @@ const listAllProtocolsForFile = (document) => { return servers.all().map(server => server.protocol()).join(','); }; + +/** + * Cleanup temporary ZIP files + */ const tidyUp = async () => { - fs.unlinkSync(TEMP_ZIP_NAME); + if (fs.existsSync(TEMP_ZIP_NAME)) { + fs.unlinkSync(TEMP_ZIP_NAME); + } }; From 3d58cda47d9c9af3bbb661b92fc63959ef0e2885 Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:21:31 +0530 Subject: [PATCH 4/7] refactor entire buildCLIListFromExamples --- scripts/fetch-asyncapi-example.js | 65 +++++++++++++++++++------------ 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index 61426dc7..7f3c9581 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -73,45 +73,60 @@ const unzipAsyncAPIExamples = async () => { }); }; +/** + * Build CLI examples list from parsed specs + */ const buildCLIListFromExamples = async () => { const files = fs.readdirSync(EXAMPLE_DIRECTORY); - const examples = files.filter(file => file.includes('.yml')).sort(); - - const buildExampleList = examples.map(async example => { - const examplePath = path.join(EXAMPLE_DIRECTORY, example); - const exampleContent = fs.readFileSync(examplePath, { encoding: 'utf-8' }); + const examples = files.filter((file) => file.endsWith('.yml')).sort(); - try { - const { document } = await parser.parse(exampleContent); - // Failed for some reason to parse this spec file (document is undefined), ignore for now - if (!document) { - return; - } + const exampleEntries = await Promise.all( + examples.map(async (example) => { + const examplePath = path.join(EXAMPLE_DIRECTORY, example); + const exampleContent = fs.readFileSync(examplePath, 'utf-8'); - const title = document.info().title(); - const protocols = listAllProtocolsForFile(document); - return { - name: protocols ? `${title} - (protocols: ${protocols})` : title, - value: example - }; - } catch (error) { - console.error(error); - } - }); + try { + const { document } = await parser.parse(exampleContent); + if (!document) { + return null; + } - const exampleList = (await Promise.all(buildExampleList)).filter(item => !!item); - const orderedExampleList = exampleList.sort((a, b) => a.name.localeCompare(b.name)); + const title = document.info().title(); + const protocols = listAllProtocolsForFile(document); - fs.writeFileSync(path.join(EXAMPLE_DIRECTORY, 'examples.json'), JSON.stringify(orderedExampleList, null, 4)); + return { + name: protocols ? `${title} - (protocols: ${protocols})` : title, + value: example, + }; + } catch (error) { + console.error(error); + return null; + } + }) + ); + + const orderedExampleList = exampleEntries + .filter(Boolean) + .sort((a, b) => a.name.localeCompare(b.name)); + + fs.writeFileSync( + path.join(EXAMPLE_DIRECTORY, 'examples.json'), + JSON.stringify(orderedExampleList, null, 2), + 'utf-8' + ); }; +/** + * List all protocols defined in an AsyncAPI document + */ + const listAllProtocolsForFile = (document) => { const servers = document.servers(); if (servers.length === 0) { return ''; } - return servers.all().map(server => server.protocol()).join(','); + return servers.all().map((server) => server.protocol()).join(','); }; From b99457733d6c745e66ada3231c046a862f037c3b Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:23:18 +0530 Subject: [PATCH 5/7] refactor: fetch examples ZIP from asyncAPI spec repository --- scripts/fetch-asyncapi-example.js | 32 +++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index 7f3c9581..71222ad6 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -25,26 +25,20 @@ const SPEC_EXAMPLES_ZIP_URL = 'https://github.com/asyncapi/spec/archive/refs/hea const EXAMPLE_DIRECTORY = path.join(__dirname, '../assets/examples'); const TEMP_ZIP_NAME = 'spec-examples.zip'; -const fetchAsyncAPIExamplesFromExternalURL = () => { - try { - return new Promise((resolve, reject) => { - fetch(SPEC_EXAMPLES_ZIP_URL) - .then(async (res) => { - if (res.status !== 200) { - return reject(new Error(`Failed to fetch examples from ${SPEC_EXAMPLES_ZIP_URL}`)); - } - - const file = fs.createWriteStream(TEMP_ZIP_NAME); - await streamPipeline(res.body, file); - - console.log('Fetched ZIP file'); - resolve(); - }) - .catch(reject); - }); - } catch (error) { - console.error(error); +/** + * Fetch examples ZIP from AsyncAPI spec repository + */ +const fetchAsyncAPIExamplesFromExternalURL = async () => { + const res = await fetch(SPEC_EXAMPLES_ZIP_URL); + + if (res.status !== 200) { + throw new Error(`Failed to fetch examples from ${SPEC_EXAMPLES_ZIP_URL}`); } + + const fileStream = fs.createWriteStream(TEMP_ZIP_NAME); + await streamPipeline(res.body, fileStream); + + console.log('Fetched ZIP file'); }; const unzipAsyncAPIExamples = async () => { From 3bd4308fbd96c8669030bd38eb03bed45508c06f Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:27:29 +0530 Subject: [PATCH 6/7] refactor unzip asyncapi examples --- scripts/fetch-asyncapi-example.js | 51 +++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index 71222ad6..ca4399b3 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -41,32 +41,57 @@ const fetchAsyncAPIExamplesFromExternalURL = async () => { console.log('Fetched ZIP file'); }; +/** + * Safely unzip examples while preventing ZIP Slip attacks + */ const unzipAsyncAPIExamples = async () => { - return new Promise((resolve, reject) => { - if (!fs.existsSync(EXAMPLE_DIRECTORY)) { - fs.mkdirSync(EXAMPLE_DIRECTORY); - } + if (!fs.existsSync(EXAMPLE_DIRECTORY)) { + fs.mkdirSync(EXAMPLE_DIRECTORY, { recursive: true }); + } + return new Promise((resolve, reject) => { fs.createReadStream(TEMP_ZIP_NAME) .pipe(unzipper.Parse()) .on('entry', async (entry) => { - const fileName = entry.path; - if (fileName.includes('examples/') && fileName.includes('.yml') && entry.type === 'File') { - const fileContent = await entry.buffer(); - const fileNameWithExtension = fileName.split('examples/')[1]; - fs.writeFileSync(path.join(EXAMPLE_DIRECTORY, fileNameWithExtension), fileContent.toString()); - } else { + try { + const rawPath = entry.path; + const normalizedPath = path.normalize(rawPath); + + // Only allow files inside examples/ directory + if ( + entry.type === 'File' && + normalizedPath.startsWith(`examples${path.sep}`) && + normalizedPath.endsWith('.yml') + ) { + const safeFileName = path.basename(normalizedPath); + const outputPath = path.join(EXAMPLE_DIRECTORY, safeFileName); + + // Final safety check to prevent path traversal + if (!outputPath.startsWith(EXAMPLE_DIRECTORY)) { + throw new Error(`Path traversal attempt detected: ${rawPath}`); + } + + const fileContent = await entry.buffer(); + fs.writeFileSync(outputPath, fileContent.toString(), 'utf-8'); + } else { + entry.autodrain(); + } + } catch (error) { entry.autodrain(); + reject(error); } - }).on('close', () => { + }) + .on('close', () => { console.log('Unzipped all examples from ZIP'); resolve(); - }).on('error', (error) => { - reject(new Error(`Error in unzipping from ZIP: ${error.message}`)); + }) + .on('error', (error) => { + reject(new Error(`Error unzipping ZIP: ${error.message}`)); }); }); }; + /** * Build CLI examples list from parsed specs */ From ac160af1a7c47a5cd8a1808616f01f54ac5e4f35 Mon Sep 17 00:00:00 2001 From: Darshan Thakare <143271270+DarshanCode2005@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:32:16 +0530 Subject: [PATCH 7/7] fix the CI/CD --- scripts/fetch-asyncapi-example.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index ca4399b3..1e3b5d48 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -159,7 +159,14 @@ const tidyUp = async () => { }; -await fetchAsyncAPIExamplesFromExternalURL(); -await unzipAsyncAPIExamples(); -await buildCLIListFromExamples(); -await tidyUp(); +const main = async () => { + await fetchAsyncAPIExamplesFromExternalURL(); + await unzipAsyncAPIExamples(); + await buildCLIListFromExamples(); + await tidyUp(); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +});