From a0f74b1fea31ae9df9e30caf789366e59353a0b0 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:54:39 +0530 Subject: [PATCH 1/4] UN-2894 Fix n8n cloud review issues - Remove dependency overrides from package.json - Remove prepublishOnly script, add validate script instead - Add pairedItem linkage to all returnData.push() calls - Replace helpers.request() with helpers.httpRequestWithAuthentication() - Update LLMWhispererApi credentials to use unstract-key header - Add build:dev script for fast development builds - Update workflows to use validate script for comprehensive checks - Update package-lock.json All blocking issues from n8n cloud review have been addressed. --- .github/workflows/pr-checks.yml | 2 +- credentials/LLMWhispererApi.credentials.ts | 2 +- nodes/LlmWhisperer/LlmWhisperer.node.ts | 16 ++++------------ nodes/Unstract/Unstract.node.ts | 12 +++--------- .../UnstractHitlFetch/UnstractHitlFetch.node.ts | 16 +++++++++------- nodes/UnstractHitlPush/UnstractHitlPush.node.ts | 16 ++++++---------- package-lock.json | 15 +++++++++++++++ package.json | 6 ++---- 8 files changed, 41 insertions(+), 44 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 0934c30..6da23a5 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -16,7 +16,7 @@ jobs: run: npm ci - name: Run comprehensive checks - run: npm run prepublishOnly + run: npm run validate - name: Validate package structure run: npm pack --dry-run diff --git a/credentials/LLMWhispererApi.credentials.ts b/credentials/LLMWhispererApi.credentials.ts index 85bf703..2660211 100644 --- a/credentials/LLMWhispererApi.credentials.ts +++ b/credentials/LLMWhispererApi.credentials.ts @@ -29,7 +29,7 @@ export class LLMWhispererApi implements ICredentialType { type: 'generic', properties: { headers: { - 'Authorization': '=Bearer apiKey', + 'unstract-key': '={{$credentials.apiKey}}', }, }, }; diff --git a/nodes/LlmWhisperer/LlmWhisperer.node.ts b/nodes/LlmWhisperer/LlmWhisperer.node.ts index 7cc5051..08352d2 100644 --- a/nodes/LlmWhisperer/LlmWhisperer.node.ts +++ b/nodes/LlmWhisperer/LlmWhisperer.node.ts @@ -195,8 +195,6 @@ export class LlmWhisperer implements INodeType { const returnData: INodeExecutionData[] = []; try { - const credentials = await this.getCredentials('llmWhispererApi'); - const apiKey = credentials.apiKey as string; const { helpers, logger } = this; for (let i = 0; i < items.length; i++) { @@ -231,7 +229,6 @@ export class LlmWhisperer implements INodeType { method: 'POST' as IHttpRequestMethods, url: `${host}/api/v2/whisper`, headers: { - 'unstract-key': `${apiKey}`, 'Content-Type': 'application/octet-stream', }, body: fileBuffer, @@ -256,7 +253,7 @@ export class LlmWhisperer implements INodeType { logger.info('Making API request to LLMWhisperer API...'); let result: any; try { - result = await helpers.request(requestOptions); + result = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', requestOptions); } catch (requestError) { logger.error('Error during LLMWhisperer API request:', requestError); throw requestError; @@ -276,12 +273,9 @@ export class LlmWhisperer implements INodeType { while (status !== 'processed' && status !== 'error') { await sleep(2000); - const statusResult = await helpers.request({ + const statusResult = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', { method: 'GET' as IHttpRequestMethods, url: `${host}/api/v2/whisper-status`, - headers: { - 'unstract-key': `${apiKey}`, - }, qs: { whisper_hash: whisperHash, }, @@ -311,12 +305,9 @@ export class LlmWhisperer implements INodeType { } if (status === 'processed') { - const retrieveResult = await helpers.request({ + const retrieveResult = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', { method: 'GET' as IHttpRequestMethods, url: `${host}/api/v2/whisper-retrieve`, - headers: { - 'unstract-key': `${apiKey}`, - }, qs: { whisper_hash: whisperHash, }, @@ -327,6 +318,7 @@ export class LlmWhisperer implements INodeType { delete retrieveResultContent.webhook_metadata; returnData.push({ json: retrieveResultContent, + pairedItem: { item: i }, }); } } diff --git a/nodes/Unstract/Unstract.node.ts b/nodes/Unstract/Unstract.node.ts index 20d57f4..d74b14c 100644 --- a/nodes/Unstract/Unstract.node.ts +++ b/nodes/Unstract/Unstract.node.ts @@ -112,7 +112,6 @@ export class Unstract implements INodeType { try { const credentials = await this.getCredentials('unstractApi'); - const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; const { helpers, logger } = this; @@ -158,15 +157,12 @@ export class Unstract implements INodeType { const requestOptions = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, - headers: { - 'Authorization': `Bearer ${apiKey}`, - }, formData, timeout: 5 * 60 * 1000, }; logger.info('Making API request to Unstract API...'); - const result = await helpers.request(requestOptions); + const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); let resultContent = JSON.parse(result).message; let executionStatus = resultContent.execution_status; @@ -180,14 +176,11 @@ export class Unstract implements INodeType { const statusRequestOptions = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, - headers: { - 'Authorization': `Bearer ${apiKey}`, - }, timeout: 5 * 60 * 1000, }; try { - const statusResult = await helpers.request(statusRequestOptions); + const statusResult = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', statusRequestOptions); resultContent = JSON.parse(statusResult); executionStatus = resultContent.status; } catch (error: any) { @@ -217,6 +210,7 @@ export class Unstract implements INodeType { returnData.push({ json: resultContent, + pairedItem: { item: i }, }); } diff --git a/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts b/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts index 7378abf..74779c9 100644 --- a/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts +++ b/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts @@ -56,7 +56,6 @@ export class UnstractHitlFetch implements INodeType { const returnData: INodeExecutionData[] = []; const credentials = await this.getCredentials('unstractHITLApi'); - const hitlKey = credentials.HITLKey as string; const orgId = credentials.orgId as string; const helpers = this.helpers; @@ -73,23 +72,26 @@ export class UnstractHitlFetch implements INodeType { const options = { method: 'GET' as IHttpRequestMethods, url, - headers: { - Authorization: `Bearer ${hitlKey}`, - }, }; try { - const response = await helpers.request(options); + const response = await helpers.httpRequestWithAuthentication.call(this, 'unstractHITLApi', options); const parsed = typeof response === 'string' ? JSON.parse(response) : response; if (parsed.error) { if (parsed.error === 'No approved items available.') { - returnData.push({ json: { message: 'No approved items available', hasData: false } }); + returnData.push({ + json: { message: 'No approved items available', hasData: false }, + pairedItem: { item: i } + }); } else { throw new NodeOperationError(this.getNode(), `API Error: ${parsed.error}`); } } else if (parsed.data) { - returnData.push({ json: { ...parsed.data, hasData: true } }); + returnData.push({ + json: { ...parsed.data, hasData: true }, + pairedItem: { item: i } + }); } else { throw new NodeOperationError(this.getNode(), 'Unexpected response format.'); } diff --git a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts index bc8d0a4..9b769e2 100644 --- a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts +++ b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts @@ -114,7 +114,6 @@ export class UnstractHitlPush implements INodeType { try { const credentials = await this.getCredentials('unstractApi'); - const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; const { helpers, logger } = this; @@ -159,15 +158,12 @@ export class UnstractHitlPush implements INodeType { const requestOptions = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, - headers: { - Authorization: `Bearer ${apiKey}`, - }, formData, timeout: 5 * 60 * 1000, }; logger.info('[HITL] Sending file to Unstract HITL API...'); - const result = await helpers.request(requestOptions); + const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); let resultContent = JSON.parse(result).message; let executionStatus = resultContent.execution_status; @@ -181,14 +177,11 @@ export class UnstractHitlPush implements INodeType { const pollRequest = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, - headers: { - Authorization: `Bearer ${apiKey}`, - }, timeout: 5 * 60 * 1000, }; try { - const pollResult = await helpers.request(pollRequest); + const pollResult = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', pollRequest); resultContent = JSON.parse(pollResult); executionStatus = resultContent.status; } catch (error: any) { @@ -216,7 +209,10 @@ export class UnstractHitlPush implements INodeType { } } - returnData.push({ json: resultContent }); + returnData.push({ + json: resultContent, + pairedItem: { item: i } + }); } return [returnData]; diff --git a/package-lock.json b/package-lock.json index 55a290b..a300140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3428,6 +3428,21 @@ "zod": "3.24.1" } }, + "node_modules/n8n-workflow/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package.json b/package.json index e34bbe1..bc2841e 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,12 @@ "main": "index.js", "scripts": { "build": "npx rimraf dist && tsc && gulp build:icons", + "build:dev": "npx rimraf dist && tsc && gulp build:icons", "dev": "tsc --watch", "format": "prettier nodes credentials --write", "lint": "eslint nodes/**/*.ts credentials/**/*.ts package.json", "lintfix": "eslint nodes/**/*.ts credentials/**/*.ts package.json --fix", - "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json && npm audit --audit-level=high" + "validate": "npm run build && eslint -c .eslintrc.prepublish.js nodes credentials package.json" }, "files": [ "dist", @@ -70,8 +71,5 @@ }, "peerDependencies": { "n8n-workflow": "*" - }, - "overrides": { - "form-data": "^4.0.4" } } From d2e014d15da0d8e2b74337e6acb5539eba71f24b Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:32:49 +0530 Subject: [PATCH 2/4] refactor: separate audit check from validation script - Add dedicated 'audit' script for security checks with high severity threshold - Remove audit from 'validate' script to allow independent execution - Update package-lock.json to reflect current dependency tree --- package-lock.json | 57 ++++++++++------------------------------------- package.json | 1 + 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index a300140..c25de9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -841,18 +841,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", - "license": "MIT", - "peer": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -1460,22 +1448,6 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2029,23 +2001,6 @@ "node": ">=0.10.0" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "peer": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs-mkdirp-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", @@ -3428,6 +3383,18 @@ "zod": "3.24.1" } }, + "node_modules/n8n-workflow/node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/n8n-workflow/node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", diff --git a/package.json b/package.json index bc2841e..50ebca1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "format": "prettier nodes credentials --write", "lint": "eslint nodes/**/*.ts credentials/**/*.ts package.json", "lintfix": "eslint nodes/**/*.ts credentials/**/*.ts package.json --fix", + "audit": "npm audit --audit-level=high", "validate": "npm run build && eslint -c .eslintrc.prepublish.js nodes credentials package.json" }, "files": [ From d912f6f7f726682661698f7b7e5b94bb7ddba1d5 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:35:05 +0530 Subject: [PATCH 3/4] ci: add security audit step to PR checks - Add npm audit step after validation - Set continue-on-error to true for visibility without blocking PRs - Audit failures will be visible in CI but won't fail the workflow --- .github/workflows/pr-checks.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 6da23a5..8988f1b 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -17,7 +17,11 @@ jobs: - name: Run comprehensive checks run: npm run validate - + + - name: Run security audit + run: npm run audit + continue-on-error: true + - name: Validate package structure run: npm pack --dry-run From c5c6ebe3fdca4e61aca8db71764461fb095e9803 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:28:01 +0530 Subject: [PATCH 4/4] refactor: remove httpbin credential tests per n8n review - Remove test property from all three credential classes: - LLMWhispererApi - UnstractApi - UnstractHITLApi - Remove unused ICredentialTestRequest import - httpbin tests did not actually validate credentials (would pass with any value) - Following n8n recommendation to remove rather than use fake validation endpoints Addresses n8n community node review feedback. --- credentials/LLMWhispererApi.credentials.ts | 12 ------------ credentials/UnstractApi.credentials.ts | 12 ------------ credentials/UnstractHITLApi.credentials.ts | 12 ------------ 3 files changed, 36 deletions(-) diff --git a/credentials/LLMWhispererApi.credentials.ts b/credentials/LLMWhispererApi.credentials.ts index 2660211..1d0a4a2 100644 --- a/credentials/LLMWhispererApi.credentials.ts +++ b/credentials/LLMWhispererApi.credentials.ts @@ -1,7 +1,6 @@ import { ICredentialType, INodeProperties, - ICredentialTestRequest, IAuthenticateGeneric, } from 'n8n-workflow'; @@ -33,15 +32,4 @@ export class LLMWhispererApi implements ICredentialType { }, }, }; - - // Using external test endpoint to satisfy n8n verification requirements - // LLMWhisperer API does not provide a dedicated test connection endpoint - // httpbin.org/bearer accepts any Bearer token and returns success - test: ICredentialTestRequest = { - request: { - baseURL: 'https://httpbin.org', - url: '/bearer', - method: 'GET', - }, - }; } \ No newline at end of file diff --git a/credentials/UnstractApi.credentials.ts b/credentials/UnstractApi.credentials.ts index f8d7074..06aa8a1 100644 --- a/credentials/UnstractApi.credentials.ts +++ b/credentials/UnstractApi.credentials.ts @@ -1,7 +1,6 @@ import { ICredentialType, INodeProperties, - ICredentialTestRequest, IAuthenticateGeneric, } from 'n8n-workflow'; @@ -41,15 +40,4 @@ export class UnstractApi implements ICredentialType { }, }, }; - - // Using external test endpoint to satisfy n8n verification requirements - // Unstract API does not provide a dedicated test connection endpoint - // httpbin.org/bearer accepts any Bearer token and returns success - test: ICredentialTestRequest = { - request: { - baseURL: 'https://httpbin.org', - url: '/bearer', - method: 'GET', - }, - }; } \ No newline at end of file diff --git a/credentials/UnstractHITLApi.credentials.ts b/credentials/UnstractHITLApi.credentials.ts index d555da0..e18de2c 100644 --- a/credentials/UnstractHITLApi.credentials.ts +++ b/credentials/UnstractHITLApi.credentials.ts @@ -1,7 +1,6 @@ import { ICredentialType, INodeProperties, - ICredentialTestRequest, IAuthenticateGeneric, } from 'n8n-workflow'; @@ -41,15 +40,4 @@ export class UnstractHITLApi implements ICredentialType { }, }, }; - - // Using external test endpoint to satisfy n8n verification requirements - // Unstract HITL API does not provide a dedicated test connection endpoint - // httpbin.org/bearer accepts any Bearer token and returns success - test: ICredentialTestRequest = { - request: { - baseURL: 'https://httpbin.org', - url: '/bearer', - method: 'GET', - }, - }; } \ No newline at end of file