From 43b5611a39a7daeb4bcea62f3054122f5d29ccfe Mon Sep 17 00:00:00 2001
From: P-Courteille
Date: Wed, 19 Nov 2025 10:18:39 +0100
Subject: [PATCH 1/3] WhiteBlackList
---
README.md | 14 ++++++++++++
package.json | 6 ++++++
src/interfaces.ts | 4 ++++
src/plugin.ts | 25 ++++++++++++++++++++--
src/proxy.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++++
src/utils/proxy.ts | 15 +++++++++++--
6 files changed, 113 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index accfb80..30d7dde 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,20 @@ If you need to use a custom certificate, it can be done by passing `certdirector
Please keep the same directory structure as the existing certificate folder.
+## Whitelist/Blacklist
+
+If you need to limit the calls going through the proxy, it can be done by passing `whitelisteddomains` and `blacklisteddomains` as an argument of the plugin:
+
+Example of `whitelisteddomains`: only the call in the domain `*.mydomain.com` got through the proxy.
+
+`appium server -ka 800 --use-plugins=appium-interceptor --plugin-appium-interceptor-whitelisteddomains='["*.mydomain.com"]' -pa /wd/hub`
+
+Example of `blacklisteddomains`: all the call go through the proxy, except the one from `*.otherdomain.com`.
+
+`appium server -ka 800 --use-plugins=appium-interceptor --plugin-appium-interceptor-blacklisteddomains='["*.otherdomain.com"]' -pa /wd/hub`
+
+Note: `whitelisteddomains` and `blacklisteddomains` are two different approach and are not supposed to be used together. If both are present, `blacklisteddomains` will be ignored.
+
## what does this plugin do?
For every appium session, interceptor plugin will start a proxy server and updates the device proxy settings to pass all network traffic to proxy server. Mocking is disabled by default and can be enabled from the test by passing `appium:intercept : true` in the desired capability while creating a new appium session.
diff --git a/package.json b/package.json
index 79cc122..d68f465 100644
--- a/package.json
+++ b/package.json
@@ -78,6 +78,12 @@
"properties": {
"certdirectory": {
"type": "string"
+ },
+ "whitelisteddomains": {
+ "type": "string"
+ },
+ "blacklisteddomains": {
+ "type": "string"
}
},
"title": "Appium interceptor plugin",
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 6176ff3..55e7020 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -2,8 +2,12 @@ import path from 'path';
export interface IPluginArgs {
certdirectory: string;
+ whitelisteddomains: string[] | string;
+ blacklisteddomains: string[] | string;
}
export const DefaultPluginArgs: IPluginArgs = {
certdirectory: path.join(__dirname, '..', 'certificate'),
+ whitelisteddomains: [],
+ blacklisteddomains: [],
};
\ No newline at end of file
diff --git a/src/plugin.ts b/src/plugin.ts
index 76c7c90..406e62c 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -5,7 +5,7 @@ import { CliArg, ISessionCapability, MockConfig, RecordConfig, RequestInfo, Repl
import { DefaultPluginArgs, IPluginArgs } from './interfaces';
import _ from 'lodash';
import { configureWifiProxy, isRealDevice, getGlobalProxyValue } from './utils/adb';
-import { cleanUpProxyServer, sanitizeMockConfig, setupProxyServer } from './utils/proxy';
+import { cleanUpProxyServer, parseJson, sanitizeMockConfig, setupProxyServer } from './utils/proxy';
import proxyCache from './proxy-cache';
import logger from './logger';
import log from './logger';
@@ -94,6 +94,19 @@ export class AppiumInterceptorPlugin extends BasePlugin {
const interceptFlag = mergedCaps['appium:intercept'];
const { deviceUDID, platformName } = response.value[1];
const certDirectory = this.pluginArgs.certdirectory;
+ const whitelistedDomains =
+ ((domains) =>
+ Array.isArray(domains) ? domains : typeof domains === 'string' ? [domains] : [])(
+ typeof this.pluginArgs.whitelisteddomains === 'string'
+ ? parseJson(this.pluginArgs.whitelisteddomains)
+ : this.pluginArgs.whitelisteddomains
+ );
+ const blacklistedDomains =
+ ((domains) => (Array.isArray(domains) ? domains : typeof domains === 'string' ? [domains] : []))(
+ typeof this.pluginArgs.blacklisteddomains === 'string'
+ ? parseJson(this.pluginArgs.blacklisteddomains)
+ : this.pluginArgs.blacklisteddomains
+ );
const sessionId = response.value[0];
const adb = driver.sessions[sessionId]?.adb;
@@ -104,7 +117,15 @@ export class AppiumInterceptorPlugin extends BasePlugin {
}
const realDevice = await isRealDevice(adb, deviceUDID);
const currentGlobalProxy = await getGlobalProxyValue(adb, deviceUDID)
- const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice, certDirectory, currentGlobalProxy);
+ const proxy = await setupProxyServer(
+ sessionId,
+ deviceUDID,
+ realDevice,
+ certDirectory,
+ currentGlobalProxy,
+ whitelistedDomains,
+ blacklistedDomains
+ );
await configureWifiProxy(adb, deviceUDID, realDevice, proxy.options);
proxyCache.add(sessionId, proxy);
}
diff --git a/src/proxy.ts b/src/proxy.ts
index b09eef5..a559b1e 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -1,5 +1,6 @@
import { MockConfig, RecordConfig, RequestInfo, SniffConfig } from './types';
import { Proxy as HttpProxy, IContext, IProxyOptions } from 'http-mitm-proxy';
+import * as net from 'net';
import { ProxyAgent } from 'proxy-agent';
import { v4 as uuid } from 'uuid';
import {
@@ -30,6 +31,8 @@ export interface ProxyOptions {
port: number;
ip: string;
previousGlobalProxy?: ProxyOptions;
+ whitelistedDomains?: string[];
+ blacklistedDomains?: string[];
}
export class Proxy {
@@ -103,6 +106,56 @@ export class Proxy {
log.info('Routing traffic via proxy-chain upstream agent');
}
+ this.httpProxy.onConnect((req, clientToProxySocket, head, callback) => {
+ const [hostname, port] = req.url!.split(':');
+ const whitelistedDomains = this.options.whitelistedDomains ?? [];
+ const blacklistedDomains = this.options.blacklistedDomains ?? [];
+
+ let shouldIntercept = true;
+ if (whitelistedDomains.length > 0) {
+ shouldIntercept = whitelistedDomains.some((domain) => doesUrlMatch(domain, hostname));
+ } else if (blacklistedDomains.length > 0) {
+ shouldIntercept = !blacklistedDomains.some((domain) => doesUrlMatch(domain, hostname));
+ }
+ if (shouldIntercept) {
+ return callback();
+ } else {
+ clientToProxySocket.on('error', (err) => {
+ log.error(`Client socket error for ${hostname}: ${err.message}`);
+ });
+
+ const proxyToServerSocket = net.connect(
+ {
+ host: hostname,
+ port: Number(port || 80),
+ },
+ () => {
+ clientToProxySocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
+ if (head && head.length > 0) {
+ proxyToServerSocket.write(head);
+ }
+ proxyToServerSocket.pipe(clientToProxySocket);
+ clientToProxySocket.pipe(proxyToServerSocket);
+ }
+ );
+
+ proxyToServerSocket.on('close', () => {
+ clientToProxySocket.end();
+ });
+
+ clientToProxySocket.on('close', () => {
+ proxyToServerSocket.end();
+ });
+
+ proxyToServerSocket.on('error', (err) => {
+ log.error(`[Tunnel] Server socket error for ${hostname}: ${err.message}`);
+ clientToProxySocket.end();
+ });
+
+ return;
+ }
+ });
+
this.httpProxy.onRequest(
RequestInterceptor((requestData: any) => {
for (const sniffer of this.sniffers.values()) {
diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts
index 5383fb8..a1fedd6 100644
--- a/src/utils/proxy.ts
+++ b/src/utils/proxy.ts
@@ -113,12 +113,23 @@ export async function setupProxyServer(
deviceUDID: string,
isRealDevice: boolean,
certDirectory: string,
- currentGlobalProxy?: ProxyOptions
+ currentGlobalProxy?: ProxyOptions,
+ whitelistedDomains?: string[],
+ blacklistedDomains?: string[]
) {
const certificatePath = prepareCertificate(sessionId, certDirectory);
const port = await getPort();
const _ip = isRealDevice ? 'localhost' : ip.address('public', 'ipv4');
- const proxy = new Proxy({ deviceUDID, sessionId, certificatePath, port, ip: _ip, previousGlobalProxy: currentGlobalProxy });
+ const proxy = new Proxy({
+ deviceUDID,
+ sessionId,
+ certificatePath,
+ port,
+ ip: _ip,
+ previousGlobalProxy: currentGlobalProxy,
+ whitelistedDomains,
+ blacklistedDomains,
+ });
await proxy.start();
if (!proxy.isStarted()) {
throw new Error('Unable to start the proxy server');
From 99b38ae01092dc8ffd9ee9d6363054c753052503 Mon Sep 17 00:00:00 2001
From: P-Courteille
Date: Wed, 19 Nov 2025 10:24:54 +0100
Subject: [PATCH 2/3] Readme
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 30d7dde..8831868 100644
--- a/README.md
+++ b/README.md
@@ -34,11 +34,11 @@ Please keep the same directory structure as the existing certificate folder.
If you need to limit the calls going through the proxy, it can be done by passing `whitelisteddomains` and `blacklisteddomains` as an argument of the plugin:
-Example of `whitelisteddomains`: only the call in the domain `*.mydomain.com` got through the proxy.
+Example of `whitelisteddomains`: only the calls for the domain `*.mydomain.com` got through the proxy.
`appium server -ka 800 --use-plugins=appium-interceptor --plugin-appium-interceptor-whitelisteddomains='["*.mydomain.com"]' -pa /wd/hub`
-Example of `blacklisteddomains`: all the call go through the proxy, except the one from `*.otherdomain.com`.
+Example of `blacklisteddomains`: all the calls go through the proxy, except the calls for `*.otherdomain.com`.
`appium server -ka 800 --use-plugins=appium-interceptor --plugin-appium-interceptor-blacklisteddomains='["*.otherdomain.com"]' -pa /wd/hub`
From a79f649fe298dfff74aeada482dc980f224dd7be Mon Sep 17 00:00:00 2001
From: P-Courteille
Date: Wed, 19 Nov 2025 10:29:32 +0100
Subject: [PATCH 3/3] bump up version
---
package-lock.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 1d036bb..00f1bac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "appium-interceptor",
- "version": "1.0.3",
+ "version": "1.0.4",
"license": "ISC",
"dependencies": {
"@appium/support": "^7.0.0",