bypass-vpn 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/bin/bypass-vpn.js +196 -0
- package/package.json +32 -0
- package/src/gateway.js +62 -0
- package/src/platform.js +37 -0
- package/src/resolver.js +42 -0
- package/src/router.js +65 -0
- package/src/services.js +38 -0
- package/src/ui.js +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# bypass-vpn
|
|
2
|
+
|
|
3
|
+
Route AI service traffic (Claude, ChatGPT, Firebase, Google Auth) through your Wi-Fi gateway to bypass VPN routing.
|
|
4
|
+
|
|
5
|
+
Works on **macOS** and **Windows**. Zero dependencies.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g bypass-vpn
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# macOS
|
|
17
|
+
sudo npx bypass-vpn
|
|
18
|
+
|
|
19
|
+
# Windows (run from elevated PowerShell)
|
|
20
|
+
npx bypass-vpn
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Route all AI services through Wi-Fi
|
|
27
|
+
sudo bypass-vpn
|
|
28
|
+
|
|
29
|
+
# Route specific services only
|
|
30
|
+
sudo bypass-vpn --service claude --service chatgpt
|
|
31
|
+
|
|
32
|
+
# Remove routes
|
|
33
|
+
sudo bypass-vpn --remove
|
|
34
|
+
|
|
35
|
+
# Preview without executing
|
|
36
|
+
sudo bypass-vpn --dry-run
|
|
37
|
+
|
|
38
|
+
# List available services
|
|
39
|
+
bypass-vpn --list
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Supported Services
|
|
43
|
+
|
|
44
|
+
| Service | Domains |
|
|
45
|
+
|---------|---------|
|
|
46
|
+
| Claude | api.anthropic.com |
|
|
47
|
+
| ChatGPT | chatgpt.com, chat.openai.com, api.openai.com, + 5 more |
|
|
48
|
+
| Firebase | firestore.googleapis.com, securetoken.googleapis.com, + 3 more |
|
|
49
|
+
| Google Auth | accounts.google.com, oauth2.googleapis.com, + 2 more |
|
|
50
|
+
|
|
51
|
+
Run `bypass-vpn --list` for the full domain list.
|
|
52
|
+
|
|
53
|
+
## How It Works
|
|
54
|
+
|
|
55
|
+
1. Detects your Wi-Fi gateway IP
|
|
56
|
+
2. Resolves each service domain to its current IP addresses
|
|
57
|
+
3. Adds host-specific routes through the Wi-Fi gateway, bypassing VPN's default route
|
|
58
|
+
|
|
59
|
+
Routes are ephemeral — they reset on reboot or network change. Re-run as needed.
|
|
60
|
+
|
|
61
|
+
## Requirements
|
|
62
|
+
|
|
63
|
+
- Node.js >= 16
|
|
64
|
+
- macOS or Windows
|
|
65
|
+
- Root/Administrator access (needed to modify routing table)
|
|
66
|
+
|
|
67
|
+
## Uninstall
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm uninstall -g bypass-vpn
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { showBanner, showSummary, c, Spinner } = require('../src/ui');
|
|
4
|
+
const { ensureAdmin } = require('../src/platform');
|
|
5
|
+
const { detect } = require('../src/gateway');
|
|
6
|
+
const { resolveAll } = require('../src/resolver');
|
|
7
|
+
const { addRoute, removeRoute } = require('../src/router');
|
|
8
|
+
const services = require('../src/services');
|
|
9
|
+
const { version } = require('../package.json');
|
|
10
|
+
|
|
11
|
+
// ── Arg parsing ────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const flags = {
|
|
15
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
16
|
+
version: args.includes('--version') || args.includes('-v'),
|
|
17
|
+
remove: args.includes('--remove') || args.includes('-r'),
|
|
18
|
+
dryRun: args.includes('--dry-run'),
|
|
19
|
+
list: args.includes('--list'),
|
|
20
|
+
services: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < args.length; i++) {
|
|
24
|
+
if (args[i] === '--service' && args[i + 1]) {
|
|
25
|
+
flags.services.push(args[i + 1].toLowerCase());
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Help ───────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
if (flags.help) {
|
|
33
|
+
console.log(`
|
|
34
|
+
${c.bold('bypass-vpn')} v${version}
|
|
35
|
+
Route AI service traffic through Wi-Fi gateway to bypass VPN.
|
|
36
|
+
|
|
37
|
+
${c.bold('Usage:')}
|
|
38
|
+
${c.cyan('sudo')} bypass-vpn Add routes (macOS)
|
|
39
|
+
bypass-vpn Add routes (Windows, elevated)
|
|
40
|
+
bypass-vpn ${c.dim('--remove')} Remove previously added routes
|
|
41
|
+
bypass-vpn ${c.dim('--dry-run')} Show commands without executing
|
|
42
|
+
bypass-vpn ${c.dim('--service claude')} Route specific service(s) only
|
|
43
|
+
bypass-vpn ${c.dim('--list')} List available services
|
|
44
|
+
|
|
45
|
+
${c.bold('Flags:')}
|
|
46
|
+
-h, --help Show this help
|
|
47
|
+
-v, --version Show version
|
|
48
|
+
-r, --remove Remove routes instead of adding
|
|
49
|
+
--dry-run Print route commands without executing
|
|
50
|
+
--service <name> Route only specified service (repeatable)
|
|
51
|
+
--list List services and their domains
|
|
52
|
+
|
|
53
|
+
${c.bold('Services:')} claude, chatgpt, firebase, googleauth
|
|
54
|
+
|
|
55
|
+
${c.bold('Examples:')}
|
|
56
|
+
sudo npx bypass-vpn
|
|
57
|
+
sudo bypass-vpn --service claude --service chatgpt
|
|
58
|
+
sudo bypass-vpn --remove
|
|
59
|
+
`);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Version ────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
if (flags.version) {
|
|
66
|
+
console.log(version);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── List ───────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
if (flags.list) {
|
|
73
|
+
console.log('');
|
|
74
|
+
for (const [key, svc] of Object.entries(services)) {
|
|
75
|
+
console.log(` ${c.bold(svc.name)} ${c.dim(`(--service ${key})`)}`);
|
|
76
|
+
for (const d of svc.domains) {
|
|
77
|
+
console.log(` ${c.dim('-')} ${d}`);
|
|
78
|
+
}
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Main ───────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
showBanner();
|
|
88
|
+
|
|
89
|
+
if (!flags.dryRun) {
|
|
90
|
+
ensureAdmin();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Detect gateway
|
|
94
|
+
const spinner = new Spinner();
|
|
95
|
+
spinner.start('Detecting Wi-Fi gateway...');
|
|
96
|
+
|
|
97
|
+
const gateway = detect();
|
|
98
|
+
if (!gateway) {
|
|
99
|
+
spinner.stop(c.red('x'), c.red('No Wi-Fi gateway found — connect to Wi-Fi first!'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
spinner.stop(c.green('OK'), `Gateway: ${c.bold(gateway)}`);
|
|
103
|
+
|
|
104
|
+
// Select services
|
|
105
|
+
let selectedServices;
|
|
106
|
+
if (flags.services.length > 0) {
|
|
107
|
+
selectedServices = {};
|
|
108
|
+
for (const key of flags.services) {
|
|
109
|
+
if (!services[key]) {
|
|
110
|
+
console.error(c.red(` Unknown service: ${key}`));
|
|
111
|
+
console.error(c.dim(` Available: ${Object.keys(services).join(', ')}`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
selectedServices[key] = services[key];
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
selectedServices = services;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Process each service
|
|
121
|
+
const allIps = new Set();
|
|
122
|
+
let totalRouted = 0;
|
|
123
|
+
let totalSkipped = 0;
|
|
124
|
+
let totalFailed = 0;
|
|
125
|
+
|
|
126
|
+
for (const [, svc] of Object.entries(selectedServices)) {
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(` ${c.bold(svc.name)} ${c.dim(`(${svc.domains.length} domain${svc.domains.length > 1 ? 's' : ''})`)}`);
|
|
129
|
+
|
|
130
|
+
const { resolved, failed } = await resolveAll(svc.domains);
|
|
131
|
+
|
|
132
|
+
for (const domain of failed) {
|
|
133
|
+
console.log(` ${c.yellow('--')} ${c.dim(domain)} ${c.dim('— no A records')}`);
|
|
134
|
+
totalSkipped++;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const [domain, ips] of resolved) {
|
|
138
|
+
const newIps = ips.filter((ip) => !allIps.has(ip));
|
|
139
|
+
const dupeCount = ips.length - newIps.length;
|
|
140
|
+
|
|
141
|
+
if (newIps.length === 0) {
|
|
142
|
+
console.log(` ${c.yellow('--')} ${c.dim(domain)} ${c.dim('— IPs already routed')}`);
|
|
143
|
+
totalSkipped++;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let hostFailed = false;
|
|
148
|
+
for (const ip of newIps) {
|
|
149
|
+
if (flags.remove) {
|
|
150
|
+
const result = removeRoute(ip);
|
|
151
|
+
if (!result.success) hostFailed = true;
|
|
152
|
+
} else {
|
|
153
|
+
const result = addRoute(ip, gateway, { dryRun: flags.dryRun });
|
|
154
|
+
if (flags.dryRun) {
|
|
155
|
+
console.log(` ${c.dim('$')} ${c.dim(result.cmd)}`);
|
|
156
|
+
}
|
|
157
|
+
if (!result.success) hostFailed = true;
|
|
158
|
+
}
|
|
159
|
+
allIps.add(ip);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!flags.dryRun) {
|
|
163
|
+
const action = flags.remove ? 'removed' : 'routed';
|
|
164
|
+
const ipStr = newIps.join(', ');
|
|
165
|
+
const dupeNote = dupeCount > 0 ? c.dim(` (+${dupeCount} dupes)`) : '';
|
|
166
|
+
if (hostFailed) {
|
|
167
|
+
console.log(` ${c.red('x')} ${domain} — failed`);
|
|
168
|
+
totalFailed++;
|
|
169
|
+
} else {
|
|
170
|
+
console.log(` ${c.green('OK')} ${domain} ${c.dim(`-> ${ipStr}`)}${dupeNote}`);
|
|
171
|
+
totalRouted++;
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
totalRouted++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!flags.dryRun) {
|
|
180
|
+
showSummary({ routed: totalRouted, skipped: totalSkipped, failed: totalFailed });
|
|
181
|
+
if (!flags.remove) {
|
|
182
|
+
console.log(` ${c.dim('To remove these routes later:')} sudo bypass-vpn --remove`);
|
|
183
|
+
console.log('');
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(` ${c.cyan(c.bold('Dry run complete.'))} No routes were modified.`);
|
|
188
|
+
console.log(` ${c.dim(`${totalRouted} domain(s) would be routed, ${totalSkipped} skipped.`)}`);
|
|
189
|
+
console.log('');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main().catch((err) => {
|
|
194
|
+
console.error(c.red(` Error: ${err.message}`));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bypass-vpn",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Route AI service traffic (Claude, ChatGPT, Firebase) through Wi-Fi gateway to bypass VPN",
|
|
5
|
+
"bin": {
|
|
6
|
+
"bypass-vpn": "./bin/bypass-vpn.js"
|
|
7
|
+
},
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=16.0.0"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"src/"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vpn",
|
|
17
|
+
"bypass",
|
|
18
|
+
"route",
|
|
19
|
+
"ai",
|
|
20
|
+
"claude",
|
|
21
|
+
"chatgpt",
|
|
22
|
+
"firebase",
|
|
23
|
+
"networking",
|
|
24
|
+
"wifi"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/ProjectAJ14/bypass-ai-vpn"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/gateway.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const { getPlatform } = require('./platform');
|
|
3
|
+
|
|
4
|
+
function detect() {
|
|
5
|
+
const platform = getPlatform();
|
|
6
|
+
|
|
7
|
+
if (platform === 'darwin') {
|
|
8
|
+
return detectMac();
|
|
9
|
+
}
|
|
10
|
+
if (platform === 'win32') {
|
|
11
|
+
return detectWindows();
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function detectMac() {
|
|
17
|
+
try {
|
|
18
|
+
const output = execSync('netstat -nr', { encoding: 'utf8', timeout: 5000 });
|
|
19
|
+
for (const line of output.split('\n')) {
|
|
20
|
+
const parts = line.trim().split(/\s+/);
|
|
21
|
+
if (parts[0] === 'default' && parts[parts.length - 1] === 'en0') {
|
|
22
|
+
return parts[1];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
|
|
27
|
+
// Fallback: networksetup
|
|
28
|
+
try {
|
|
29
|
+
const output = execSync('networksetup -getinfo Wi-Fi', { encoding: 'utf8', timeout: 5000 });
|
|
30
|
+
const match = output.match(/^Router:\s+(.+)$/m);
|
|
31
|
+
if (match) return match[1].trim();
|
|
32
|
+
} catch {}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function detectWindows() {
|
|
38
|
+
// Try PowerShell Get-NetRoute
|
|
39
|
+
try {
|
|
40
|
+
const cmd = `powershell -NoProfile -Command "Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Where-Object { $_.InterfaceAlias -like '*Wi-Fi*' -or $_.InterfaceAlias -like '*Wireless*' } | Select-Object -First 1 -ExpandProperty NextHop"`;
|
|
41
|
+
const output = execSync(cmd, { encoding: 'utf8', timeout: 10000 }).trim();
|
|
42
|
+
if (output && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(output)) {
|
|
43
|
+
return output;
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
// Fallback: parse ipconfig
|
|
48
|
+
try {
|
|
49
|
+
const output = execSync('ipconfig', { encoding: 'utf8', timeout: 5000 });
|
|
50
|
+
const sections = output.split(/\r?\n\r?\n/);
|
|
51
|
+
for (const section of sections) {
|
|
52
|
+
if (/Wi-Fi|Wireless/i.test(section)) {
|
|
53
|
+
const match = section.match(/Default Gateway[.\s]*:\s*([\d.]+)/);
|
|
54
|
+
if (match) return match[1];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { detect };
|
package/src/platform.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const { c } = require('./ui');
|
|
3
|
+
|
|
4
|
+
function getPlatform() {
|
|
5
|
+
const p = process.platform;
|
|
6
|
+
if (p === 'darwin' || p === 'win32') return p;
|
|
7
|
+
return 'unsupported';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function ensureAdmin() {
|
|
11
|
+
const platform = getPlatform();
|
|
12
|
+
|
|
13
|
+
if (platform === 'unsupported') {
|
|
14
|
+
console.error(c.red(' Unsupported platform. Only macOS and Windows are supported.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (platform === 'darwin') {
|
|
19
|
+
if (process.getuid() !== 0) {
|
|
20
|
+
console.error(c.yellow(' This tool needs root privileges to modify routes.'));
|
|
21
|
+
console.error(c.dim(' Run: sudo bypass-vpn'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (platform === 'win32') {
|
|
27
|
+
try {
|
|
28
|
+
execSync('net session', { stdio: 'ignore' });
|
|
29
|
+
} catch {
|
|
30
|
+
console.error(c.yellow(' This tool needs Administrator privileges to modify routes.'));
|
|
31
|
+
console.error(c.dim(' Run from an elevated Command Prompt or PowerShell.'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { getPlatform, ensureAdmin };
|
package/src/resolver.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const dns = require('dns');
|
|
2
|
+
const dnsPromises = dns.promises;
|
|
3
|
+
|
|
4
|
+
async function resolveAll(domains) {
|
|
5
|
+
const resolved = new Map();
|
|
6
|
+
const failed = [];
|
|
7
|
+
|
|
8
|
+
const results = await Promise.allSettled(
|
|
9
|
+
domains.map(async (domain) => {
|
|
10
|
+
const ips = await withTimeout(dnsPromises.resolve4(domain), 5000);
|
|
11
|
+
return { domain, ips };
|
|
12
|
+
})
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
for (const result of results) {
|
|
16
|
+
if (result.status === 'fulfilled') {
|
|
17
|
+
const { domain, ips } = result.value;
|
|
18
|
+
if (ips && ips.length > 0) {
|
|
19
|
+
resolved.set(domain, ips);
|
|
20
|
+
} else {
|
|
21
|
+
failed.push(domain);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
// Extract domain from the original array by index
|
|
25
|
+
const idx = results.indexOf(result);
|
|
26
|
+
failed.push(domains[idx]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { resolved, failed };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function withTimeout(promise, ms) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timer = setTimeout(() => reject(new Error('DNS timeout')), ms);
|
|
36
|
+
promise
|
|
37
|
+
.then((val) => { clearTimeout(timer); resolve(val); })
|
|
38
|
+
.catch((err) => { clearTimeout(timer); reject(err); });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { resolveAll };
|
package/src/router.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const { getPlatform } = require('./platform');
|
|
3
|
+
|
|
4
|
+
const IP_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
5
|
+
|
|
6
|
+
function validateIp(ip) {
|
|
7
|
+
if (!IP_REGEX.test(ip)) {
|
|
8
|
+
throw new Error(`Invalid IP address: ${ip}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function addRoute(ip, gateway, { dryRun = false } = {}) {
|
|
13
|
+
validateIp(ip);
|
|
14
|
+
validateIp(gateway);
|
|
15
|
+
|
|
16
|
+
const platform = getPlatform();
|
|
17
|
+
let cmd;
|
|
18
|
+
|
|
19
|
+
if (platform === 'darwin') {
|
|
20
|
+
// Remove existing route first (ignore errors)
|
|
21
|
+
const delCmd = `route -n delete -host ${ip} 2>/dev/null || true`;
|
|
22
|
+
cmd = `route -n add -host ${ip} ${gateway}`;
|
|
23
|
+
if (dryRun) {
|
|
24
|
+
return { success: true, ip, cmd };
|
|
25
|
+
}
|
|
26
|
+
try { execSync(delCmd, { stdio: 'ignore' }); } catch {}
|
|
27
|
+
} else {
|
|
28
|
+
// Windows: remove existing then add
|
|
29
|
+
const delCmd = `route delete ${ip}`;
|
|
30
|
+
cmd = `route add ${ip} mask 255.255.255.255 ${gateway}`;
|
|
31
|
+
if (dryRun) {
|
|
32
|
+
return { success: true, ip, cmd };
|
|
33
|
+
}
|
|
34
|
+
try { execSync(delCmd, { stdio: 'ignore' }); } catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
39
|
+
return { success: true, ip };
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return { success: false, ip, error: err.message };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function removeRoute(ip) {
|
|
46
|
+
validateIp(ip);
|
|
47
|
+
|
|
48
|
+
const platform = getPlatform();
|
|
49
|
+
let cmd;
|
|
50
|
+
|
|
51
|
+
if (platform === 'darwin') {
|
|
52
|
+
cmd = `route -n delete -host ${ip}`;
|
|
53
|
+
} else {
|
|
54
|
+
cmd = `route delete ${ip}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
59
|
+
return { success: true, ip };
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return { success: false, ip, error: err.message };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { addRoute, removeRoute };
|
package/src/services.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
claude: {
|
|
3
|
+
name: 'Claude',
|
|
4
|
+
domains: ['api.anthropic.com'],
|
|
5
|
+
},
|
|
6
|
+
chatgpt: {
|
|
7
|
+
name: 'ChatGPT',
|
|
8
|
+
domains: [
|
|
9
|
+
'chatgpt.com',
|
|
10
|
+
'ab.chatgpt.com',
|
|
11
|
+
'chat.openai.com',
|
|
12
|
+
'api.openai.com',
|
|
13
|
+
'auth0.openai.com',
|
|
14
|
+
'cdn.oaistatic.com',
|
|
15
|
+
'files.oaiusercontent.com',
|
|
16
|
+
'events.statsigapi.net',
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
firebase: {
|
|
20
|
+
name: 'Firebase',
|
|
21
|
+
domains: [
|
|
22
|
+
'firestore.googleapis.com',
|
|
23
|
+
'securetoken.googleapis.com',
|
|
24
|
+
'identitytoolkit.googleapis.com',
|
|
25
|
+
'narad-muni-14.firebaseapp.com',
|
|
26
|
+
'narad-muni-14.firebasestorage.app',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
googleauth: {
|
|
30
|
+
name: 'Google Auth',
|
|
31
|
+
domains: [
|
|
32
|
+
'accounts.google.com',
|
|
33
|
+
'oauth2.googleapis.com',
|
|
34
|
+
'www.googleapis.com',
|
|
35
|
+
'iamcredentials.googleapis.com',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
};
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { version } = require('../package.json');
|
|
2
|
+
|
|
3
|
+
const c = {
|
|
4
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
5
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
6
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
7
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
8
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
9
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const SPIN_FRAMES = ['|', '/', '-', '\\'];
|
|
13
|
+
|
|
14
|
+
class Spinner {
|
|
15
|
+
constructor() {
|
|
16
|
+
this._interval = null;
|
|
17
|
+
this._frame = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
start(text) {
|
|
21
|
+
this._frame = 0;
|
|
22
|
+
this._interval = setInterval(() => {
|
|
23
|
+
const frame = c.cyan(SPIN_FRAMES[this._frame % SPIN_FRAMES.length]);
|
|
24
|
+
process.stderr.write(`\r ${frame} ${text}`);
|
|
25
|
+
this._frame++;
|
|
26
|
+
}, 80);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stop(symbol, text) {
|
|
30
|
+
if (this._interval) {
|
|
31
|
+
clearInterval(this._interval);
|
|
32
|
+
this._interval = null;
|
|
33
|
+
}
|
|
34
|
+
process.stderr.write(`\r ${symbol} ${text}\x1b[K\n`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function showBanner() {
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(c.bold(c.cyan(' ╭─────────────────────────────────────╮')));
|
|
41
|
+
console.log(c.bold(c.cyan(` │ bypass-vpn v${version.padEnd(18)}│`)));
|
|
42
|
+
console.log(c.bold(c.cyan(' │ Route AI traffic around your VPN │')));
|
|
43
|
+
console.log(c.bold(c.cyan(' ╰─────────────────────────────────────╯')));
|
|
44
|
+
console.log('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function showSummary({ routed, skipped, failed }) {
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(c.bold(c.cyan(' ╭─────────────────────────────────────╮')));
|
|
50
|
+
console.log(c.bold(c.cyan(' │ Results │')));
|
|
51
|
+
console.log(c.bold(c.cyan(' ├─────────────────────────────────────┤')));
|
|
52
|
+
console.log(c.bold(c.cyan(' │')) + ` ${c.green('Routed:')} ${String(routed + ' host(s)').padEnd(26)}` + c.bold(c.cyan('│')));
|
|
53
|
+
console.log(c.bold(c.cyan(' │')) + ` ${c.yellow('Skipped:')} ${String(skipped + ' host(s)').padEnd(26)}` + c.bold(c.cyan('│')));
|
|
54
|
+
console.log(c.bold(c.cyan(' │')) + ` ${c.red('Failed:')} ${String(failed + ' host(s)').padEnd(26)}` + c.bold(c.cyan('│')));
|
|
55
|
+
console.log(c.bold(c.cyan(' ╰─────────────────────────────────────╯')));
|
|
56
|
+
console.log('');
|
|
57
|
+
|
|
58
|
+
if (failed === 0) {
|
|
59
|
+
console.log(` ${c.green(c.bold('All clear!'))} VPN bypassed for AI services.`);
|
|
60
|
+
} else {
|
|
61
|
+
console.log(` ${c.yellow(c.bold('Partial success.'))} Some routes failed — see above.`);
|
|
62
|
+
}
|
|
63
|
+
console.log('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { c, Spinner, showBanner, showSummary };
|