From 7c8c5a6ebc1faaf0b98fc63734a6f1c5dfb1ac53 Mon Sep 17 00:00:00 2001 From: Md Asif Raza Date: Wed, 22 Oct 2025 23:37:29 +0530 Subject: [PATCH 1/5] feat: add dynamic expression valiadtion message --- src/enum/httpRequest.enum.ts | 16 +++--- src/proxy/testflow/testflow.service.ts | 80 ++++++++++++++++++-------- src/utils/decode-testflow.ts | 8 +-- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/enum/httpRequest.enum.ts b/src/enum/httpRequest.enum.ts index 485347d..4ed9e56 100644 --- a/src/enum/httpRequest.enum.ts +++ b/src/enum/httpRequest.enum.ts @@ -1,11 +1,3 @@ -export interface KeyWrapper { - key: string; -} - -export interface ValueWrapper { - value: string; -} - export enum RequestDataTypeEnum { JSON = "JSON", XML = "XML", @@ -30,4 +22,10 @@ export enum ResponseStatusCode { ERROR = "Not Found", } -export interface KeyValue extends KeyWrapper, ValueWrapper {} \ No newline at end of file +export interface KeyValue { + key: string; + value: string; + checked?: boolean; + base?: string; + type?: "text" | "file"; +} \ No newline at end of file diff --git a/src/proxy/testflow/testflow.service.ts b/src/proxy/testflow/testflow.service.ts index 4afe1ee..57978b0 100644 --- a/src/proxy/testflow/testflow.service.ts +++ b/src/proxy/testflow/testflow.service.ts @@ -109,30 +109,63 @@ export class TestflowService { } private async validateUrl(targetUrl: string) { + let url: URL; + try { - const url = new URL(targetUrl); - - // Resolve hostname to IPs - const addresses = await lookup(url.hostname, { all: true }); - - for (const addr of addresses) { - const ip = ipaddr.parse(addr.address); - - // Block local, private, or reserved IPs - if ( - ip.range() === 'linkLocal' || // 169.254.0.0/16 (Azure IMDS lives here) - ip.range() === 'loopback' || // 127.0.0.0/8 - ip.range() === 'private' || // 10.x, 192.168.x, 172.16-31.x - ip.range() === 'reserved' // Other reserved ranges - ) { - throw new BadRequestException( - `Access to internal IP addresses is not allowed: ${addr.address}`, - ); - } - } - } catch (err) { - throw new BadRequestException('Invalid or disallowed URL'); + url = new URL(targetUrl); + } catch (error) { + throw new BadRequestException('Invalid URL'); } + + const hostname = url.hostname.toLowerCase(); + + const localhostPatterns = [ + 'localhost', + '127.0.0.1', + '0.0.0.0', + '::1', // IPv6 localhost + ]; + + // Check for private IP ranges + const isPrivateIP = ( + hostname.startsWith('192.168.') || + hostname.startsWith('10.') || + (hostname.startsWith('172.') && + (() => { + const parts = hostname.split('.'); + if (parts.length >= 2) { + const secondOctet = parseInt(parts[1], 10); + return secondOctet >= 16 && secondOctet <= 31; + } + return false; + })() + ) || + hostname.endsWith('.local') || + hostname.includes('.local.') + ); + + const isLocalhost = localhostPatterns.includes(hostname) || isPrivateIP; + + if (isLocalhost) { + throw new BadRequestException('This API is local and cannot run on the cloud. Deploy it to enable execution.'); + } + if (targetUrl.startsWith('/') || targetUrl.startsWith('./') || !targetUrl.includes('://')) { + throw new BadRequestException('This API is local and cannot run on the cloud. Deploy it to enable execution.'); + } + } + + private async validateFormData( + body: any, + contentType: string, + ) { + if (contentType === 'multipart/form-data' && body) { + const formData = JSON.parse(body); + formData.find((item: any) => { + if (item?.type === 'file') { + throw new BadRequestException('This API includes form-data with file uploads that are stored locally and cannot run in scheduled/cloud execution. Remove or replace the files to enable execution.'); + } + }); + } } private async makeHttpRequest({ @@ -150,6 +183,7 @@ export class TestflowService { }): Promise<{ status: string; data: any; headers: any }> { try { await this.validateUrl(url); + await this.validateFormData(body, contentType); // Parse headers from stringified JSON const parsedHeaders: Record = {}; let headersArray; @@ -329,7 +363,7 @@ export class TestflowService { } } } catch (error: any) { - console.error('HTTP Service Error:', error); + // console.error('HTTP Service Error:', error); throw new Error(error.message || 'Unknown error occurred'); } } diff --git a/src/utils/decode-testflow.ts b/src/utils/decode-testflow.ts index db914b7..13edb57 100644 --- a/src/utils/decode-testflow.ts +++ b/src/utils/decode-testflow.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import JSON5 from "json5" -import type { KeyValue} from "src/enum/httpRequest.enum"; +import type { KeyValue } from "src/enum/httpRequest.enum"; import { RequestDataTypeEnum } from "src/enum/httpRequest.enum"; /** @@ -96,13 +96,13 @@ class DecodeTestflow { * Return only checked KeyValue entries (preserves order). */ private extractKeyValue = ( - pairs?: Array<{ key: string; value: string; checked: boolean }>, + pairs?: KeyValue[], ): KeyValue[] => { if (!Array.isArray(pairs)) return []; const checkedPairs: KeyValue[] = []; for (const pair of pairs) { if (pair && pair.checked && pair.key) { - checkedPairs.push({ key: pair.key, value: String(pair.value || "") }); + checkedPairs.push({ key: pair.key, type: pair.type, value: String(pair.value || "") }); } } return checkedPairs; @@ -482,7 +482,7 @@ class DecodeTestflow { String(field.value), environmentVariables, ), - type: "text", + type: field.type, }); }); } From cbef2028333b21ed87e57915810a0da94cd51e92 Mon Sep 17 00:00:00 2001 From: Md Asif Raza Date: Thu, 23 Oct 2025 12:28:13 +0530 Subject: [PATCH 2/5] feat: add private ip check --- src/proxy/testflow/testflow.service.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/proxy/testflow/testflow.service.ts b/src/proxy/testflow/testflow.service.ts index 57978b0..950daad 100644 --- a/src/proxy/testflow/testflow.service.ts +++ b/src/proxy/testflow/testflow.service.ts @@ -149,9 +149,6 @@ export class TestflowService { if (isLocalhost) { throw new BadRequestException('This API is local and cannot run on the cloud. Deploy it to enable execution.'); } - if (targetUrl.startsWith('/') || targetUrl.startsWith('./') || !targetUrl.includes('://')) { - throw new BadRequestException('This API is local and cannot run on the cloud. Deploy it to enable execution.'); - } } private async validateFormData( From 071a1ec1b68c77ac88b1d6cdc6e01b3df8797ff8 Mon Sep 17 00:00:00 2001 From: Aarti Panchal Date: Thu, 23 Oct 2025 14:01:57 +0530 Subject: [PATCH 3/5] feat: add production workflow --- .github/workflows/prod.yml | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/prod.yml diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml new file mode 100644 index 0000000..a31c443 --- /dev/null +++ b/.github/workflows/prod.yml @@ -0,0 +1,44 @@ +name: Production +on: + push: + branches: + - main + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + - uses: Azure/docker-login@v1 + with: + login-server: sparrowprod.azurecr.io + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - run: | + docker build . -t sparrowprod.azurecr.io/sparrow-proxy:${{ github.run_number }} + docker push sparrowprod.azurecr.io/sparrow-proxy:${{ github.run_number }} + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: richardrigutins/replace-in-files@v1 + with: + files: "./deploymentManifests/deployment.yml" + search-text: '_BUILD__ID_' + replacement-text: '${{ github.run_number }}' + + - uses: azure/setup-kubectl@v2.0 + + - uses: Azure/k8s-set-context@v2 + with: + kubeconfig: ${{ secrets.KUBE_CONFIG }} + + - uses: Azure/k8s-deploy@v4 + with: + action: deploy + namespace: sparrow-prod + manifests: | + ./deploymentManifests/prod.yml \ No newline at end of file From 3fa1ef1ec84827370adde218110de6ea525bd4ce Mon Sep 17 00:00:00 2001 From: Aarti Panchal Date: Thu, 23 Oct 2025 17:14:14 +0530 Subject: [PATCH 4/5] feat: add production workflow --- .github/workflows/prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index a31c443..2c6cf70 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -41,4 +41,4 @@ jobs: action: deploy namespace: sparrow-prod manifests: | - ./deploymentManifests/prod.yml \ No newline at end of file + ./deploymentManifests/deployment.yml \ No newline at end of file From 159cf51dd725a191c96f5b72b0afa80d5c51abf9 Mon Sep 17 00:00:00 2001 From: Kishan Gupta Date: Tue, 28 Oct 2025 17:40:30 +0530 Subject: [PATCH 5/5] upgrade the version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04aa80b..e67b303 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sparrow-proxy", - "version": "2.32.1", + "version": "2.34.0", "description": "", "author": "", "private": true,