Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Casks/vpn-bypass.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Or if using local tap: brew install --cask --no-quarantine ./Casks/vpn-bypass.rb

cask "vpn-bypass" do
version "1.7.0"
version "1.7.1"
sha256 "37b127a55aec0bdb80e824e59e840ce5b529c09086aac7fc24dc4616abb817bd"

url "https://github.com/GeiserX/VPN-Bypass/releases/download/v#{version}/VPN-Bypass-#{version}.dmg"
Expand Down
2 changes: 1 addition & 1 deletion Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.7.0</string><!-- VERSION -->
<string>1.7.1</string><!-- VERSION -->
<key>CFBundleVersion</key>
<string>19</string>
<key>LSMinimumSystemVersion</key>
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<p align="center">
<img src="https://img.shields.io/badge/macOS-13%2B-blue" alt="macOS 13+">
<img src="https://img.shields.io/badge/Swift-5.9-orange" alt="Swift 5.9">
<a href="https://github.com/GeiserX/VPN-Bypass/releases"><img src="https://img.shields.io/badge/version-1.7.0-green" alt="Version"></a>
<a href="https://github.com/GeiserX/VPN-Bypass/releases"><img src="https://img.shields.io/badge/version-1.7.1-green" alt="Version"></a>
</p>

## Why?
Expand Down
28 changes: 10 additions & 18 deletions Sources/RouteManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ final class RouteManager: ObservableObject {
var vpnCandidates: [(name: String, ip: String, isValid: Bool)] = []

for i in interfaces.indices {
let isValid = await isCorporateVPNIP(interfaces[i].ip)
let isValid = await isCorporateVPNIP(interfaces[i].ip, hintType: hintType)
interfaces[i].isValidCorporateIP = isValid

// Track VPN candidates for debugging
Expand Down Expand Up @@ -765,7 +765,8 @@ final class RouteManager: ObservableObject {
}

/// Check if IP is likely a corporate VPN (not Tailscale mesh, not localhost, etc.)
private func isCorporateVPNIP(_ ip: String) async -> Bool {
/// hintType comes from process detection -- used to distinguish Zscaler/WARP from Tailscale in the shared CGNAT range.
private func isCorporateVPNIP(_ ip: String, hintType: VPNType?) async -> Bool {
let parts = ip.components(separatedBy: ".")
guard parts.count == 4,
let first = Int(parts[0]),
Expand All @@ -779,29 +780,20 @@ final class RouteManager: ObservableObject {
// Skip link-local
if first == 169 && second == 254 { return false }

// Tailscale CGNAT range (100.64.0.0/10 = 100.64-127.x.x)
// Only consider Tailscale as VPN if it's using an exit node (routing all traffic)
// CGNAT range (100.64.0.0/10 = 100.64-127.x.x)
// Shared by Tailscale, Zscaler, Cloudflare WARP, and other VPNs.
// If a known non-Tailscale VPN process was detected, trust it.
// Otherwise fall back to Tailscale exit-node check.
if first == 100 && second >= 64 && second <= 127 {
if let hint = hintType, hint != .tailscale, hint != .unknown {
return true
}
return await isTailscaleExitNodeActive()
}

// Cloudflare WARP range (check for WARP-specific IPs)
// WARP uses 100.96.0.0/12 range
if first == 100 && second >= 96 && second <= 111 {
return true // WARP is active
}

// Zscaler typically uses 100.64.x.x or custom ranges
// Already covered by CGNAT check above

// Corporate VPNs typically use private ranges
// 10.0.0.0/8 - Most corporate VPNs use this
if first == 10 { return true }

// 172.16.0.0/12 (172.16-31.x.x)
if first == 172 && second >= 16 && second <= 31 { return true }

// 192.168.0.0/16 - Less common for VPN but possible
if first == 192 && second == 168 { return true }

return false
Expand Down
4 changes: 2 additions & 2 deletions Sources/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ struct GeneralTab: View {
HStack {
VStack(alignment: .leading, spacing: 2) {
BrandedAppName(fontSize: 13)
Text("Version 1.7.0")
Text("Version 1.7.1")
.font(.system(size: 11))
.foregroundColor(Color(hex: "6B7280"))
}
Expand Down Expand Up @@ -1721,7 +1721,7 @@ struct InfoTab: View {
// App name with branded colors
BrandedAppName(fontSize: 24)

Text("v1.7.0")
Text("v1.7.1")
.font(.system(size: 12, design: .monospaced))
.foregroundColor(Color(hex: "6B7280"))

Expand Down
5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to VPN Bypass will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.7.1] - 2026-02-24

### Fixed
- **Zscaler Detection** - Zscaler (and Cloudflare WARP) use CGNAT IPs (`100.64.x.x`) which were incorrectly treated as Tailscale-only, causing `valid=false` rejection. Now trusts the process-detection hint to distinguish Zscaler/WARP from Tailscale in the shared CGNAT range.

## [1.7.0] - 2026-02-22

### Added
Expand Down