Setting the file. One moment.
Platform Bot Protection · Vercel Optimize · vercel-labs/agent-skills · Skills Docs
ContentsBack to the top of the page lib/gates/platform-bot-protection.mjs
lib/gates/ platform-bot-protection.mjs
JavaScript · 115 lines · 5 KB
{
9 id: 'platform_bot_protection' ,
10 threshold: 'botIdEnabled=false AND (botPct >= 0.05 OR edge_cost >= $25/window OR requests >= 14k/14d)' ,
11 billingDimension: 'edge-requests' ,
12 scope: 'account' ,
13 sourceCitation: 'vercel-optimize gate threshold' ,
14 description:
15 'When BotID is disabled AND there is evidence (observed bot bandwidth share, edge cost, or substantial request volume) that bot traffic is non-trivial. Bot traffic inflates edge request counts without delivering user value; staged bot protection can reduce waste on bot-heavy projects. Skipped on quiet projects with no bot evidence — the recommendation would be noise.' ,
16 };
17
18 export function gate ( signals ) {
19 // BotID surfaces under several legacy fields; check all.
20 const botEnabled =
21 signals.project?.security?.botIdEnabled === true
22 || signals.project?.security?.botProtection === true
23 || signals.project?.botProtection?.enabled === true
24 || signals.project?.delegatedProtection?.bot === true ;
25 if (botEnabled) return [];
26
27 // Project config failed — we can't tell if BotID is on, so stay silent.
28 if (signals.project?.error) return [];
29
30 const totalRequests = totalRequestsFromSignals (signals);
31 const botShare = computeBotShare (signals);
32 const edgeService = (signals.usage?.services ?? []). find (
33 ( s ) => /edge . request/ i . test (s.name ?? '' )
34 );
35 const edgeCost = edgeService?.billedCost ?? null ;
36
37 // Require observable bot share, edge cost, OR substantial traffic — otherwise rec is just config nagging.
38 const hasObservedBots = botShare?.botPct != null && botShare.botPct >= MIN_BOT_PCT ;
39 const hasMaterialEdgeCost = edgeCost != null && edgeCost >= MIN_EDGE_COST ;
40 const hasSubstantialTraffic = totalRequests >= MIN_TOTAL_REQUESTS ;
41 if ( ! hasObservedBots && ! hasMaterialEdgeCost && ! hasSubstantialTraffic) return [];
42
43 const challengeRule = signals.project?.security?.managedRules?.bot_filter;
44 const ruleNote = challengeRule?.active
45 ? `firewall bot_filter rule active (action=${ challengeRule . action ?? '?'})`
46 : 'no firewall bot_filter rule' ;
47
48 // Kicker on high observed bot share — harder evidence than config alone.
49 let priority = edgeCost != null ? Math. max ( 20 , Math. round (edgeCost)) : 30 ;
50 if (botShare?.botPct != null && botShare.botPct > 0.2 ) priority += 20 ;
51
52 // Confidence bumps when we can SEE bot traffic, not just infer from config.
53 let confidence = edgeCost != null ? 0.85 : 0.6 ;
54 if (botShare?.botPct != null && botShare.botPct > 0.2 ) confidence = Math. min ( 0.95 , confidence + 0.05 );
55
56 const botShareNote = botShare?.botPct != null
57 ? `bot_fdt_pct=${ ( botShare . botPct * 100 ). toFixed ( 0 ) }%`
58 : 'bot_fdt_pct=unknown' ;
59
60 return [{
61 kind: metadata.id,
62 scope: 'account' ,
63 files: [],
64 priority,
65 confidence,
66 o11ySignal: edgeCost != null
67 ? `edge_cost=${ edgeCost . toFixed ( 0 ) },bot_protection=disabled,${ botShareNote },${ ruleNote }`
68 : `requests=${ totalRequests },bot_protection=disabled,${ botShareNote },${ ruleNote }` ,
69 reason: botShare?.botPct != null && botShare.botPct > 0.2
70 ? 'BotID disabled with observable bot bandwidth share'
71 : 'BotID disabled with observable traffic' ,
72 question: botShare?.botPct != null && botShare.botPct > 0.2
73 ? `Bot traffic accounts for ${ ( botShare . botPct * 100 ). toFixed ( 0 ) }% of FDT bytes (top category: ${ botShare . topCategory ?? 'unknown'}). Would enabling BotID + a challenge rule reduce that share?`
74 : 'Would enabling BotID (Bot Protection) reduce edge request volume from automated traffic?' ,
75 evidence: {
76 botEnabled: false ,
77 edgeCost,
78 totalRequests,
79 managedRules: challengeRule ?? null ,
80 botShare: botShare ?? null ,
81 },
82 }];
83 }
84
85 function totalRequestsFromSignals ( signals ) {
86 const rows = signals.metrics?.requestsByRouteCache?.rows;
87 if ( ! Array. isArray (rows)) return 0 ;
88 return rows. reduce (( s , r ) => s + (r.value ?? 0 ), 0 );
89 }
90
91 // CLI convention: bot_category="" means "not classified as a bot" (human + unclassified); any non-empty = bot.
92 function computeBotShare ( signals ) {
93 const rows = signals.metrics?.fdtByBot?.rows;
94 if ( ! Array. isArray (rows) || rows. length === 0 ) return null ;
95 let humanBytes = 0 ;
96 let botBytes = 0 ;
97 let topCategory = null ;
98 let topBytes = 0 ;
99 for ( const r of rows) {
100 const v = r.value ?? 0 ;
101 const cat = r.bot_category ?? '' ;
102 if (cat === '' ) {
103 humanBytes += v;
104 } else {
105 botBytes += v;
106 if (v > topBytes) {
107 topBytes = v;
108 topCategory = cat;
109 }
110 }
111 }
112 const total = humanBytes + botBytes;
113 if (total < MIN_TOTAL_FDT_BYTES ) return null ;
114 return { humanBytes, botBytes, botPct: botBytes / total, topCategory };
115 }