1- import { isHostingASN } from "asn-blocklist" ;
1+ import { classifyASN } from "asn-blocklist" ;
22import { describe , expect , it , vi } from "vitest" ;
33
44import {
@@ -10,7 +10,13 @@ import {
1010import type { Env , TrackerClientPayload } from "@/lib/edge/types" ;
1111
1212vi . mock ( "asn-blocklist" , ( ) => ( {
13- isHostingASN : vi . fn ( ( asn : unknown ) => Number ( asn ) === 13335 ) ,
13+ classifyASN : vi . fn ( ( asn : unknown ) => {
14+ if ( Number ( asn ) === 13335 ) return "hosting" ;
15+ if ( Number ( asn ) === 9009 ) return "network_service" ;
16+ if ( Number ( asn ) === 4134 ) return "transit" ;
17+ if ( Number ( asn ) === 7922 ) return "access" ;
18+ return "unknown" ;
19+ } ) ,
1420} ) ) ;
1521
1622const CHROME_UA =
@@ -103,11 +109,11 @@ describe("bot protection", () => {
103109 confidence : "medium" ,
104110 } ) ;
105111 expect ( result . reasons ) . toContain ( "hosting_asn" ) ;
106- expect ( vi . mocked ( isHostingASN ) ) . toHaveBeenCalledWith ( 13335 ) ;
112+ expect ( vi . mocked ( classifyASN ) ) . toHaveBeenCalledWith ( 13335 ) ;
107113 } ) ;
108114
109- it ( "does not classify AS organization names without a blocked ASN" , ( ) => {
110- vi . mocked ( isHostingASN ) . mockReturnValueOnce ( false ) ;
115+ it ( "does not classify AS organization names without a risky ASN class " , ( ) => {
116+ vi . mocked ( classifyASN ) . mockReturnValueOnce ( "unknown" ) ;
111117 const result = classifyCollectBotTraffic ( {
112118 request : request (
113119 {
@@ -131,6 +137,99 @@ describe("bot protection", () => {
131137 } ) ;
132138 } ) ;
133139
140+ it ( "keeps network-service ASNs on the main lane when browser provenance is present" , ( ) => {
141+ const result = classifyCollectBotTraffic ( {
142+ request : request (
143+ {
144+ "user-agent" : CHROME_UA ,
145+ origin : "https://example.com" ,
146+ "sec-fetch-site" : "cross-site" ,
147+ } ,
148+ {
149+ asn : 9009 ,
150+ asOrganization : "M247 Global" ,
151+ } ,
152+ ) ,
153+ payload,
154+ origin : "https://example.com" ,
155+ } ) ;
156+
157+ expect ( result ) . toEqual ( {
158+ isBot : false ,
159+ confidence : "low" ,
160+ reasons : [ "network_service_asn" ] ,
161+ } ) ;
162+ } ) ;
163+
164+ it ( "classifies network-service ASNs with missing browser provenance as medium confidence" , ( ) => {
165+ const result = classifyCollectBotTraffic ( {
166+ request : request (
167+ {
168+ "user-agent" : CHROME_UA ,
169+ } ,
170+ {
171+ asn : 9009 ,
172+ asOrganization : "M247 Global" ,
173+ } ,
174+ ) ,
175+ payload,
176+ origin : "https://example.com" ,
177+ } ) ;
178+
179+ expect ( result ) . toMatchObject ( {
180+ isBot : true ,
181+ confidence : "medium" ,
182+ } ) ;
183+ expect ( result . reasons ) . toEqual ( [
184+ "network_service_asn" ,
185+ "missing_browser_provenance" ,
186+ ] ) ;
187+ } ) ;
188+
189+ it ( "does not divert transit or access ASNs on ASN class alone" , ( ) => {
190+ const transit = classifyCollectBotTraffic ( {
191+ request : request (
192+ {
193+ "user-agent" : CHROME_UA ,
194+ origin : "https://example.com" ,
195+ "sec-fetch-site" : "cross-site" ,
196+ } ,
197+ {
198+ asn : 4134 ,
199+ asOrganization : "China Telecom" ,
200+ } ,
201+ ) ,
202+ payload,
203+ origin : "https://example.com" ,
204+ } ) ;
205+ const access = classifyCollectBotTraffic ( {
206+ request : request (
207+ {
208+ "user-agent" : CHROME_UA ,
209+ origin : "https://example.com" ,
210+ "sec-fetch-site" : "cross-site" ,
211+ } ,
212+ {
213+ asn : 7922 ,
214+ asOrganization : "Comcast" ,
215+ } ,
216+ ) ,
217+ payload,
218+ origin : "https://example.com" ,
219+ } ) ;
220+
221+ expect ( transit ) . toEqual ( {
222+ isBot : false ,
223+ confidence : "low" ,
224+ reasons : [ "transit_asn" ] ,
225+ } ) ;
226+ expect ( access ) . toEqual ( {
227+ isBot : false ,
228+ confidence : "low" ,
229+ reasons : [ "access_asn" ] ,
230+ } ) ;
231+ } ) ;
232+
134233 it ( "keeps normal browser requests on the main lane" , ( ) => {
135234 const result = classifyCollectBotTraffic ( {
136235 request : request ( {
0 commit comments