Skip to content

Commit 846e55c

Browse files
oschwaldclaude
andcommitted
Add IP risk and anonymizer outputs to Insights
This adds support for new risk and anonymizer data fields available in the GeoIP2 Insights web service: - New anonymizer object with VPN confidence scoring, provider name detection, and network last seen date - New ip_risk_snapshot field in traits for static IP risk scoring - Deprecated anonymizer-related methods in traits (anonymous?, anonymous_vpn?, hosting_provider?, public_proxy?, residential_proxy?, and tor_exit_node?) in favor of the new anonymizer object The deprecated methods continue to work for backward compatibility but direct users to the new anonymizer object location. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 65609b0 commit 846e55c

File tree

5 files changed

+197
-0
lines changed

5 files changed

+197
-0
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 1.5.0
4+
5+
* A new `anonymizer` object has been added to the `MaxMind::GeoIP2::Model::Insights`
6+
model. This object indicates whether the IP address is part of an anonymizing
7+
network, including VPN confidence scoring, provider name detection, and network
8+
last seen date. This is only available from the GeoIP2 Insights web service.
9+
* A new `ip_risk_snapshot` method has been added to `MaxMind::GeoIP2::Record::Traits`.
10+
This field contains the risk associated with the IP address, ranging from 0.01 to
11+
99 (a higher score indicates a higher risk). This is only available from the GeoIP2
12+
Insights web service.
13+
* The `anonymous?`, `anonymous_vpn?`, `hosting_provider?`, `public_proxy?`,
14+
`residential_proxy?`, and `tor_exit_node?` methods in
15+
`MaxMind::GeoIP2::Record::Traits` have been deprecated. Please use the
16+
corresponding methods in the `anonymizer` object from the GeoIP2 Insights
17+
response instead.
18+
319
## 1.4.0
420

521
* Ruby 3.2+ is now required. If you're using Ruby 3.0 or 3.1, please use

lib/maxmind/geoip2/model/insights.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require 'maxmind/geoip2/model/city'
4+
require 'maxmind/geoip2/record/anonymizer'
45

56
module MaxMind
67
module GeoIP2
@@ -10,6 +11,19 @@ module Model
1011
# See https://dev.maxmind.com/geoip/docs/web-services?lang=en for more
1112
# details.
1213
class Insights < City
14+
# Data indicating whether the IP address is part of an anonymizing
15+
# network.
16+
#
17+
# @return [MaxMind::GeoIP2::Record::Anonymizer]
18+
attr_reader :anonymizer
19+
20+
# @!visibility private
21+
def initialize(record, locales)
22+
super
23+
@anonymizer = MaxMind::GeoIP2::Record::Anonymizer.new(
24+
record['anonymizer'],
25+
)
26+
end
1327
end
1428
end
1529
end
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# frozen_string_literal: true
2+
3+
require 'date'
4+
require 'maxmind/geoip2/record/abstract'
5+
6+
module MaxMind
7+
module GeoIP2
8+
module Record
9+
# Contains data indicating whether an IP address is part of an
10+
# anonymizing network.
11+
#
12+
# This record is returned by the Insights web service.
13+
class Anonymizer < Abstract
14+
# A score ranging from 1 to 99 that represents our percent confidence
15+
# that the network is currently part of an actively used VPN service.
16+
# This property is only available from Insights.
17+
#
18+
# @return [Integer, nil]
19+
def confidence
20+
get('confidence')
21+
end
22+
23+
# This is true if the IP address belongs to any sort of anonymous
24+
# network. This property is only available from Insights.
25+
#
26+
# @return [Boolean]
27+
def anonymous?
28+
get('is_anonymous')
29+
end
30+
31+
# This is true if the IP address is registered to an anonymous VPN
32+
# provider. If a VPN provider does not register subnets under names
33+
# associated with them, we will likely only flag their IP ranges using
34+
# the hosting_provider? property. This property is only available from
35+
# Insights.
36+
#
37+
# @return [Boolean]
38+
def anonymous_vpn?
39+
get('is_anonymous_vpn')
40+
end
41+
42+
# This is true if the IP address belongs to a hosting or VPN provider
43+
# (see description of the anonymous_vpn? property). This property is
44+
# only available from Insights.
45+
#
46+
# @return [Boolean]
47+
def hosting_provider?
48+
get('is_hosting_provider')
49+
end
50+
51+
# This is true if the IP address belongs to a public proxy. This
52+
# property is only available from Insights.
53+
#
54+
# @return [Boolean]
55+
def public_proxy?
56+
get('is_public_proxy')
57+
end
58+
59+
# This is true if the IP address is on a suspected anonymizing network
60+
# and belongs to a residential ISP. This property is only available from
61+
# Insights.
62+
#
63+
# @return [Boolean]
64+
def residential_proxy?
65+
get('is_residential_proxy')
66+
end
67+
68+
# This is true if the IP address is a Tor exit node. This property is
69+
# only available from Insights.
70+
#
71+
# @return [Boolean]
72+
def tor_exit_node?
73+
get('is_tor_exit_node')
74+
end
75+
76+
# The last day that the network was sighted in our analysis of
77+
# anonymized networks. This value is parsed lazily. This property is
78+
# only available from Insights.
79+
#
80+
# @return [Date, nil] A Date object representing the last seen date,
81+
# or nil if the date is not available.
82+
def network_last_seen
83+
return @network_last_seen if defined?(@network_last_seen)
84+
85+
date_string = get('network_last_seen')
86+
87+
if !date_string
88+
return nil
89+
end
90+
91+
@network_last_seen = Date.parse(date_string)
92+
end
93+
94+
# The name of the VPN provider (e.g., NordVPN, SurfShark, etc.)
95+
# associated with the network. This property is only available from
96+
# Insights.
97+
#
98+
# @return [String, nil]
99+
def provider_name
100+
get('provider_name')
101+
end
102+
end
103+
end
104+
end
105+
end

lib/maxmind/geoip2/record/traits.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ def ip_address
7676
# This is true if the IP address belongs to any sort of anonymous network.
7777
# This property is only available from Insights.
7878
#
79+
# This method is deprecated as of version 1.5.0. Use the anonymizer object
80+
# from the Insights response instead.
81+
#
7982
# @return [Boolean]
83+
# @deprecated since 1.5.0
8084
def anonymous?
8185
get('is_anonymous')
8286
end
@@ -86,7 +90,11 @@ def anonymous?
8690
# associated with them, we will likely only flag their IP ranges using the
8791
# hosting_provider? property. This property is only available from Insights.
8892
#
93+
# This method is deprecated as of version 1.5.0. Use the anonymizer object
94+
# from the Insights response instead.
95+
#
8996
# @return [Boolean]
97+
# @deprecated since 1.5.0
9098
def anonymous_vpn?
9199
get('is_anonymous_vpn')
92100
end
@@ -107,7 +115,11 @@ def anycast?
107115
# description of the anonymous_vpn? property). This property is only
108116
# available from Insights.
109117
#
118+
# This method is deprecated as of version 1.5.0. Use the anonymizer object
119+
# from the Insights response instead.
120+
#
110121
# @return [Boolean]
122+
# @deprecated since 1.5.0
111123
def hosting_provider?
112124
get('is_hosting_provider')
113125
end
@@ -146,7 +158,11 @@ def mobile_network_code
146158
# This is true if the IP address belongs to a public proxy. This property
147159
# is only available from Insights.
148160
#
161+
# This method is deprecated as of version 1.5.0. Use the anonymizer object
162+
# from the Insights response instead.
163+
#
149164
# @return [Boolean]
165+
# @deprecated since 1.5.0
150166
def public_proxy?
151167
get('is_public_proxy')
152168
end
@@ -155,15 +171,23 @@ def public_proxy?
155171
# and belongs to a residential ISP. This property is only available
156172
# from Insights.
157173
#
174+
# This method is deprecated as of version 1.5.0. Use the anonymizer object
175+
# from the Insights response instead.
176+
#
158177
# @return [Boolean]
178+
# @deprecated since 1.5.0
159179
def residential_proxy?
160180
get('is_residential_proxy')
161181
end
162182

163183
# This is true if the IP address is a Tor exit node. This property is only
164184
# available from Insights.
165185
#
186+
# This method is deprecated as of version 1.5.0. Use the anonymizer object
187+
# from the Insights response instead.
188+
#
166189
# @return [Boolean]
190+
# @deprecated since 1.5.0
167191
def tor_exit_node?
168192
get('is_tor_exit_node')
169193
end
@@ -212,6 +236,19 @@ def user_count
212236
get('user_count')
213237
end
214238

239+
# This field contains the risk associated with the IP address. The value
240+
# ranges from 0.01 to 99. A higher score indicates a higher risk.
241+
# Please note that the IP risk score provided in GeoIP products and
242+
# services is more static than the IP risk score provided in minFraud
243+
# and is not responsive to traffic on your network. If you need realtime
244+
# IP risk scoring based on behavioral signals on your own network, please
245+
# use minFraud. This property is only available from Insights.
246+
#
247+
# @return [Float, nil]
248+
def ip_risk_snapshot
249+
get('ip_risk_snapshot')
250+
end
251+
215252
# The user type associated with the IP address. This can be one of the
216253
# following values:
217254
#

test/test_client.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ class ClientTest < Minitest::Test
2828
}.freeze
2929

3030
INSIGHTS = {
31+
'anonymizer' => {
32+
'confidence' => 85,
33+
'is_anonymous' => true,
34+
'is_anonymous_vpn' => true,
35+
'is_hosting_provider' => false,
36+
'is_public_proxy' => false,
37+
'is_residential_proxy' => true,
38+
'is_tor_exit_node' => false,
39+
'network_last_seen' => '2025-10-15',
40+
'provider_name' => 'NordVPN',
41+
},
3142
'continent' => {
3243
'code' => 'NA',
3344
'geoname_id' => 42,
@@ -43,6 +54,7 @@ class ClientTest < Minitest::Test
4354
},
4455
'traits' => {
4556
'ip_address' => '1.2.3.40',
57+
'ip_risk_snapshot' => 45.5,
4658
'is_anycast' => true,
4759
'is_residential_proxy' => true,
4860
'network' => '1.2.3.0/24',
@@ -86,11 +98,24 @@ def test_insights
8698

8799
assert_equal(42, record.continent.geoname_id)
88100

101+
# Test anonymizer object
102+
assert_equal(85, record.anonymizer.confidence)
103+
assert(record.anonymizer.anonymous?)
104+
assert(record.anonymizer.anonymous_vpn?)
105+
refute(record.anonymizer.hosting_provider?)
106+
refute(record.anonymizer.public_proxy?)
107+
assert(record.anonymizer.residential_proxy?)
108+
refute(record.anonymizer.tor_exit_node?)
109+
assert_equal(Date.parse('2025-10-15'), record.anonymizer.network_last_seen)
110+
assert_equal('NordVPN', record.anonymizer.provider_name)
111+
112+
# Test traits
89113
assert(record.traits.anycast?)
90114
assert(record.traits.residential_proxy?)
91115
assert_equal('1.2.3.0/24', record.traits.network)
92116
assert_in_delta(1.3, record.traits.static_ip_score)
93117
assert_equal(2, record.traits.user_count)
118+
assert_in_delta(45.5, record.traits.ip_risk_snapshot)
94119
end
95120

96121
def test_city

0 commit comments

Comments
 (0)