Skip to content

Commit c4f64dd

Browse files
committed
Allow AI chat to connect to LAN-hosted models via HTTP
Fixes #12768. Adds NSAllowsLocalNetworking to Info.plist and extends self-hosted detection to recognize private IPv4/IPv6 addresses.
1 parent 3271a28 commit c4f64dd

File tree

9 files changed

+355
-35
lines changed

9 files changed

+355
-35
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import XCTest
2+
import Network
3+
@testable import iTerm2SharedARC
4+
5+
final class PrivateIPCheckerTests: XCTestCase {
6+
7+
// MARK: - Localhost Tests
8+
9+
func testLocalhost() {
10+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("localhost"))
11+
}
12+
13+
func testLocalhostUppercase() {
14+
// DNS names are case-insensitive, but we match exactly
15+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("LOCALHOST"))
16+
}
17+
18+
// MARK: - .local Domain Tests
19+
20+
func testLocalDomain() {
21+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("myserver.local"))
22+
}
23+
24+
func testLocalDomainSubdomain() {
25+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("gpu.server.local"))
26+
}
27+
28+
func testLocalDomainNotSuffix() {
29+
// "local" appearing elsewhere shouldn't match
30+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("local.example.com"))
31+
}
32+
33+
// MARK: - IPv4 Loopback Tests
34+
35+
func testIPv4Loopback127_0_0_1() {
36+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("127.0.0.1"))
37+
}
38+
39+
func testIPv4Loopback127_0_0_2() {
40+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("127.0.0.2"))
41+
}
42+
43+
func testIPv4Loopback127_255_255_255() {
44+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("127.255.255.255"))
45+
}
46+
47+
// MARK: - IPv4 Private Range 10.x.x.x Tests
48+
49+
func testIPv4Private10_0_0_1() {
50+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("10.0.0.1"))
51+
}
52+
53+
func testIPv4Private10_255_255_255() {
54+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("10.255.255.255"))
55+
}
56+
57+
// MARK: - IPv4 Private Range 172.16-31.x.x Tests
58+
59+
func testIPv4Private172_16_0_1() {
60+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("172.16.0.1"))
61+
}
62+
63+
func testIPv4Private172_31_255_255() {
64+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("172.31.255.255"))
65+
}
66+
67+
func testIPv4NotPrivate172_15_0_1() {
68+
// 172.15.x.x is NOT private
69+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("172.15.0.1"))
70+
}
71+
72+
func testIPv4NotPrivate172_32_0_1() {
73+
// 172.32.x.x is NOT private
74+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("172.32.0.1"))
75+
}
76+
77+
// MARK: - IPv4 Private Range 192.168.x.x Tests
78+
79+
func testIPv4Private192_168_0_1() {
80+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("192.168.0.1"))
81+
}
82+
83+
func testIPv4Private192_168_255_255() {
84+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("192.168.255.255"))
85+
}
86+
87+
func testIPv4NotPrivate192_169_0_1() {
88+
// 192.169.x.x is NOT private
89+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("192.169.0.1"))
90+
}
91+
92+
// MARK: - IPv4 Link-Local Tests
93+
94+
func testIPv4LinkLocal169_254_0_1() {
95+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("169.254.0.1"))
96+
}
97+
98+
func testIPv4LinkLocal169_254_255_255() {
99+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("169.254.255.255"))
100+
}
101+
102+
func testIPv4NotLinkLocal169_255_0_1() {
103+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("169.255.0.1"))
104+
}
105+
106+
// MARK: - IPv4 Public Address Tests
107+
108+
func testIPv4Public8_8_8_8() {
109+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("8.8.8.8"))
110+
}
111+
112+
func testIPv4Public1_1_1_1() {
113+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("1.1.1.1"))
114+
}
115+
116+
// MARK: - IPv4 Hostname Spoofing Prevention Tests
117+
118+
func testIPv4SpoofedHostname192_168_example_com() {
119+
// Must NOT match - this is a hostname, not an IP
120+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("192.168.example.com"))
121+
}
122+
123+
func testIPv4SpoofedHostname10_evil_com() {
124+
// Must NOT match - this is a hostname, not an IP
125+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("10.evil.com"))
126+
}
127+
128+
func testIPv4SpoofedHostname172_16_attacker_net() {
129+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("172.16.attacker.net"))
130+
}
131+
132+
// MARK: - IPv6 Loopback Tests
133+
134+
func testIPv6LoopbackShort() {
135+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("::1"))
136+
}
137+
138+
func testIPv6LoopbackFull() {
139+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("0:0:0:0:0:0:0:1"))
140+
}
141+
142+
func testIPv6LoopbackFullWithLeadingZeros() {
143+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("0000:0000:0000:0000:0000:0000:0000:0001"))
144+
}
145+
146+
// MARK: - IPv6 Link-Local Tests (fe80::/10)
147+
148+
func testIPv6LinkLocalFe80() {
149+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("fe80::1"))
150+
}
151+
152+
func testIPv6LinkLocalFe80Full() {
153+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("fe80:0:0:0:0:0:0:1"))
154+
}
155+
156+
func testIPv6LinkLocalFebf() {
157+
// febf is still within fe80::/10
158+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("febf::1"))
159+
}
160+
161+
func testIPv6NotLinkLocalFec0() {
162+
// fec0 is outside fe80::/10 (deprecated site-local, but not link-local)
163+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("fec0::1"))
164+
}
165+
166+
// MARK: - IPv6 Unique Local Tests (fc00::/7)
167+
168+
func testIPv6UniqueLocalFc00() {
169+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("fc00::1"))
170+
}
171+
172+
func testIPv6UniqueLocalFd00() {
173+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("fd00::1"))
174+
}
175+
176+
func testIPv6UniqueLocalFdab() {
177+
XCTAssertTrue(PrivateIPChecker.isLocalOrPrivate("fdab:cdef:1234::1"))
178+
}
179+
180+
func testIPv6NotUniqueLocalFe00() {
181+
// fe00 is outside fc00::/7
182+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("fe00::1"))
183+
}
184+
185+
// MARK: - IPv6 Public Address Tests
186+
187+
func testIPv6PublicGoogle() {
188+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("2001:4860:4860::8888"))
189+
}
190+
191+
func testIPv6PublicCloudflare() {
192+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("2606:4700:4700::1111"))
193+
}
194+
195+
// MARK: - Invalid Input Tests
196+
197+
func testEmptyString() {
198+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate(""))
199+
}
200+
201+
func testRandomHostname() {
202+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("api.openai.com"))
203+
}
204+
205+
func testInvalidIPv4TooManyOctets() {
206+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("192.168.1.1.1"))
207+
}
208+
209+
func testInvalidIPv4OctetOutOfRange() {
210+
XCTAssertFalse(PrivateIPChecker.isLocalOrPrivate("192.168.1.256"))
211+
}
212+
}

iTerm2.xcodeproj/project.pbxproj

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,7 @@
928928
53FF982B2093C823008688D7 /* iTermSignatureVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FF98292093C823008688D7 /* iTermSignatureVerifier.m */; };
929929
53FF984B20967806008688D7 /* iTermMetalDeviceProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 53FF984920967805008688D7 /* iTermMetalDeviceProvider.h */; };
930930
53FF984C20967806008688D7 /* iTermMetalDeviceProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FF984A20967805008688D7 /* iTermMetalDeviceProvider.m */; };
931+
564EA2FD15A05A60A95ABBB9 /* PrivateIPChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625C956DD832F4FD0D8F9715 /* PrivateIPChecker.swift */; };
931932
5ECE005F1454E59B004861E9 /* PseudoTerminalRestorer.h in Headers */ = {isa = PBXBuildFile; fileRef = 5ECE005D1454E59B004861E9 /* PseudoTerminalRestorer.h */; };
932933
6998793AE8C54A0EAE7D5D0F /* PillBackgroundRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3AA921DF1744D7BA890104 /* PillBackgroundRenderer.swift */; };
933934
70C067D3E9484CA9A48D69BD /* ButtonPillInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E1E38CBCE5463DA81CC1CF /* ButtonPillInfo.swift */; };
@@ -5152,6 +5153,7 @@
51525153
DCE63F57A37160221DC5B939 /* iTermScreenshotPreviewOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE6718DDCFD2CA382391CDFD /* iTermScreenshotPreviewOverlay.swift */; };
51535154
DDF0FD65062916F70080EF74 /* iTermApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF0FD63062916F70080EF74 /* iTermApplication.h */; };
51545155
EE293B4DA9D2204FCFDE6784 /* iTermCursorSlideAnimator.h in Sources */ = {isa = PBXBuildFile; fileRef = 889A9128E944B618CDE75E18 /* iTermCursorSlideAnimator.h */; };
5156+
EEBDD6CF78595292D0CF8977 /* PrivateIPCheckerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86F85D24451C27760B7C75BC /* PrivateIPCheckerTests.swift */; };
51555157
F7C626F9C65F19A1BEE800DA /* iTermWordExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF1E9C79C30008CC546F987 /* iTermWordExtractor.swift */; };
51565158
FB4CEC973C7E9235362E3F26 /* iTermRemotePreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = FB4CECF4AC4392B21E87A07B /* iTermRemotePreferences.h */; };
51575159
/* End PBXBuildFile section */
@@ -6215,6 +6217,7 @@
62156217
53FF984A20967805008688D7 /* iTermMetalDeviceProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermMetalDeviceProvider.m; sourceTree = "<group>"; };
62166218
5ECE005D1454E59B004861E9 /* PseudoTerminalRestorer.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = PseudoTerminalRestorer.h; sourceTree = "<group>"; tabWidth = 4; };
62176219
5ECE005E1454E59B004861E9 /* PseudoTerminalRestorer.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = PseudoTerminalRestorer.m; sourceTree = "<group>"; tabWidth = 4; };
6220+
625C956DD832F4FD0D8F9715 /* PrivateIPChecker.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PrivateIPChecker.swift; sourceTree = "<group>"; };
62186221
698B6ED16A3CD11D402C364B /* iTermUploadIndicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = sources/iTermUploadIndicator.swift; sourceTree = SOURCE_ROOT; };
62196222
756C32AB2597AC2E0047B3A9 /* iTermImage+Sixel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iTermImage+Sixel.h"; sourceTree = "<group>"; };
62206223
756C32AC2597AC2E0047B3A9 /* iTermImage+Sixel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "iTermImage+Sixel.m"; sourceTree = "<group>"; };
@@ -6235,6 +6238,7 @@
62356238
7C74C12959E7040AF3A3B12F /* iTermAnnotatedScreenshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = iTermAnnotatedScreenshot.swift; sourceTree = "<group>"; };
62366239
7EA289FFE2BF4090A65931BA /* PillBackgroundGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillBackgroundGenerator.swift; sourceTree = "<group>"; };
62376240
7F3AA921DF1744D7BA890104 /* PillBackgroundRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillBackgroundRenderer.swift; sourceTree = "<group>"; };
6241+
86F85D24451C27760B7C75BC /* PrivateIPCheckerTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PrivateIPCheckerTests.swift; sourceTree = "<group>"; };
62386242
8742065A0564169600CFC3F1 /* iTerm2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iTerm2.app; sourceTree = BUILT_PRODUCTS_DIR; };
62396243
889A9128E944B618CDE75E18 /* iTermCursorSlideAnimator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = sources/iTermCursorSlideAnimator.h; sourceTree = SOURCE_ROOT; };
62406244
904AE312A0E0489899D9ECB8 /* MenuTips.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MenuTips.xcassets; sourceTree = "<group>"; };
@@ -9210,8 +9214,28 @@
92109214
/* End PBXFileReference section */
92119215

92129216
/* Begin PBXFileSystemSynchronizedRootGroup section */
9213-
9C0F690213C696BCC28C2EDF /* PerformanceTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = PerformanceTests; sourceTree = "<group>"; };
9214-
A6A723AF2DC04C2200A8115D /* ModernTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ModernTests; sourceTree = "<group>"; };
9217+
9C0F690213C696BCC28C2EDF /* PerformanceTests */ = {
9218+
isa = PBXFileSystemSynchronizedRootGroup;
9219+
exceptions = (
9220+
);
9221+
explicitFileTypes = {
9222+
};
9223+
explicitFolders = (
9224+
);
9225+
path = PerformanceTests;
9226+
sourceTree = "<group>";
9227+
};
9228+
A6A723AF2DC04C2200A8115D /* ModernTests */ = {
9229+
isa = PBXFileSystemSynchronizedRootGroup;
9230+
exceptions = (
9231+
);
9232+
explicitFileTypes = {
9233+
};
9234+
explicitFolders = (
9235+
);
9236+
path = ModernTests;
9237+
sourceTree = "<group>";
9238+
};
92159239
/* End PBXFileSystemSynchronizedRootGroup section */
92169240

92179241
/* Begin PBXFrameworksBuildPhase section */
@@ -9388,7 +9412,7 @@
93889412
/* End PBXFrameworksBuildPhase section */
93899413

93909414
/* Begin PBXGroup section */
9391-
0464AB0D006CD2EC7F000001 /* JTerminal */ = {
9415+
0464AB0D006CD2EC7F000001 = {
93929416
isa = PBXGroup;
93939417
children = (
93949418
A6B1C69D2DC15898008AD3CA /* ModernTests.xctestplan */,
@@ -9415,6 +9439,7 @@
94159439
1DD39AD5180B8118004E56D5 /* Frameworks */,
94169440
0464AB32006CD2EC7F000001 /* Products */,
94179441
17E422BA95C161BE34CAF68C /* ModernTests */,
9442+
4325D9EB31536D67A2479157 /* sources */,
94189443
);
94199444
name = JTerminal;
94209445
sourceTree = "<group>";
@@ -9869,6 +9894,7 @@
98699894
98E9463133DCF8311E0E5B6E /* iTermFindOnPageHelperTests.swift */,
98709895
F6601DD02DFE3C2480DFDC73 /* IntervalTreeCoordinateClampingTests.swift */,
98719896
F78529322C819CAD15A19251 /* IntervalTreeGraphEncodingTests.swift */,
9897+
86F85D24451C27760B7C75BC /* PrivateIPCheckerTests.swift */,
98729898
);
98739899
path = ModernTests;
98749900
sourceTree = "<group>";
@@ -10819,6 +10845,15 @@
1081910845
name = SSKeychain;
1082010846
sourceTree = "<group>";
1082110847
};
10848+
4325D9EB31536D67A2479157 /* sources */ = {
10849+
isa = PBXGroup;
10850+
children = (
10851+
625C956DD832F4FD0D8F9715 /* PrivateIPChecker.swift */,
10852+
);
10853+
name = sources;
10854+
path = sources;
10855+
sourceTree = "<group>";
10856+
};
1082210857
530AB7EC20AE85DE00D2AA08 /* api */ = {
1082310858
isa = PBXGroup;
1082410859
children = (
@@ -13412,24 +13447,6 @@
1341213447
name = StateRestoration;
1341313448
sourceTree = "<group>";
1341413449
};
13415-
A6B07CFB3FDCC38BFB483BDA /* sources */ = {
13416-
isa = PBXGroup;
13417-
children = (
13418-
207C83644F1E850C9F1E918A /* iTermTouchIDHelper.swift */,
13419-
B231EDB860248C368A511B42 /* iTermEventTriggerEvaluator.swift */,
13420-
DE7B0AE10E1598EDE1E01795 /* iTermEventTriggerParameterView.swift */,
13421-
7BF1E9C79C30008CC546F987 /* iTermWordExtractor.swift */,
13422-
C08AA4008946722C3F4C67C1 /* WordSelectionAtom.swift */,
13423-
02CF16386D8605E6BC44ED81 /* CharacterAtomIterator.swift */,
13424-
76421C2E762130B690D1ADC5 /* RegexAtomIterator.swift */,
13425-
889A9128E944B618CDE75E18 /* iTermCursorSlideAnimator.h */,
13426-
479FDFE9C0F424FEC10526EC /* iTermCursorSlideAnimator.m */,
13427-
ED49EE365D629DAE80503BFD /* iTermNonTextPasteHelper.swift */,
13428-
698B6ED16A3CD11D402C364B /* iTermUploadIndicator.swift */,
13429-
);
13430-
path = sources;
13431-
sourceTree = "<group>";
13432-
};
1343313450
A6B413FE211A488D00D28207 /* CompactWindowStyle */ = {
1343413451
isa = PBXGroup;
1343513452
children = (
@@ -16416,7 +16433,7 @@
1641616433
en,
1641716434
Base,
1641816435
);
16419-
mainGroup = 0464AB0D006CD2EC7F000001 /* JTerminal */;
16436+
mainGroup = 0464AB0D006CD2EC7F000001;
1642016437
packageReferences = (
1642116438
A62E3BC62E1CC674002DF8C3 /* XCLocalSwiftPackageReference "WebExtensionsFramework" */,
1642216439
);
@@ -20047,6 +20064,7 @@
2004720064
D9A2368683D7DDB73A826821 /* iTermStreamingPNGWriter.m in Sources */,
2004820065
29C503BA29752DF6A4DB2A02 /* iTermBlurRowBuffer.swift in Sources */,
2004920066
8EA9D682DE4199E114D920A5 /* iTermStreamingScreenshotEncoder.swift in Sources */,
20067+
564EA2FD15A05A60A95ABBB9 /* PrivateIPChecker.swift in Sources */,
2005020068
);
2005120069
runOnlyForDeploymentPostprocessing = 0;
2005220070
};
@@ -20095,9 +20113,8 @@
2009520113
buildActionMask = 2147483647;
2009620114
files = (
2009720115
A6C811F42DCFD5E40088E628 /* SearchEngineTests.swift in Sources */,
20098-
F54DF58519DE131885375D3F /* iTermWordExtractorTest.swift in Sources */,
20099-
02BBB9199A50961FFEAC2D6D /* iTermFindOnPageHelperTests.swift in Sources */,
2010020116
92738CD9A08C339E8F473394 /* IntervalTreeGraphEncodingTests.swift in Sources */,
20117+
EEBDD6CF78595292D0CF8977 /* PrivateIPCheckerTests.swift in Sources */,
2010120118
);
2010220119
runOnlyForDeploymentPostprocessing = 0;
2010320120
};

plists/beta-iTerm2.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<dict>
77
<key>NSAllowsArbitraryLoadsInWebContent</key>
88
<true/>
9+
<key>NSAllowsLocalNetworking</key>
10+
<true/>
911
<key>NSExceptionDomains</key>
1012
<dict>
1113
<key>127.0.0.1</key>

plists/dev-iTerm2.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<dict>
77
<key>NSAllowsArbitraryLoadsInWebContent</key>
88
<true/>
9+
<key>NSAllowsLocalNetworking</key>
10+
<true/>
911
<key>NSExceptionDomains</key>
1012
<dict>
1113
<key>127.0.0.1</key>

0 commit comments

Comments
 (0)