Skip to content

Add BotID safeguards#1654

Draft
Jhonattan2121 wants to merge 153 commits intocanaryfrom
botid-protection
Draft

Add BotID safeguards#1654
Jhonattan2121 wants to merge 153 commits intocanaryfrom
botid-protection

Conversation

@Jhonattan2121
Copy link
Collaborator

@Jhonattan2121 Jhonattan2121 commented Jan 4, 2026

  • Added [email protected] and injected BotIdProtectionLoader into src/app/layout.tsx so the client guard runs globally.
  • Added src/common/utils/botIdProtection.ts to normalize headers, call checkBotId, and replay response headers consistently.
  • BotIdProtectionLoader now feeds botIdProtectedRoutes into BotIdClient as described in the Vercel docs.
  • Protected /api/miniapp-discovery, /api/frames, /api/iframely, /api/opengraph, /api/rss, /api/venice, and /api/venice/background with enforceBotIdProtection and always reapply BotID headers before returning JSON.
  • botIdProtectedRoutes covers both GET and POST methods for those endpoints to keep client/server aligned.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added bot protection across API endpoints to prevent unauthorized automated access and enhance platform security.
  • Improvements

    • Enhanced RSS feed responses with additional metadata fields for richer content information.

✏️ Tip: You can customize this high-level summary in your review settings.

willyogo and others added 30 commits October 17, 2025 18:20
- Add explicit display: flex to all div elements in channel OG image generation
- Resolves 500 error when generating OpenGraph images for channel pages
…data API

- Replace NextApiRequest/NextApiResponse with NextRequest for edge runtime
- Use robust URL parsing instead of fragile string splitting
- Add validation for required channelId parameter
- Return proper Response objects with HTTP status codes
- Addresses coderabbitai suggestions for type safety and runtime compatibility
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#1494)

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
j-paterson and others added 12 commits December 19, 2025 13:39
- Enhance commitSpaceTabToDatabase to handle new tabs renamed before commit
- Remove registerSpaceTab (commitSpaceTabToDatabase now handles both)
- Update API endpoint to use upload() with upsert:true for create/update
- Improve error handling with proper types and statusCode checks
- Simplify commitAllSpaceChanges: remove categorization, commit all tabs
- Make createSpaceTab and renameSpaceTab staged-only (no immediate commits)
- Add deletedTabs tracking for explicit deletion management
- Fix API status code comparison: use string '404' instead of number
- Remove optimistic update pattern from renameSpaceTab (no-op commitFn)

All tab operations now use consistent staged commit pattern, committed
together via commitAllSpaceChanges for atomic batched updates.
…mplement domain->config routing, and add endpoint for registering community_configs (#1625)

Co-authored-by: Jesse Paterson <[email protected]>
Copilot AI review requested due to automatic review settings January 4, 2026 18:44
@vercel
Copy link

vercel bot commented Jan 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
space-system Ready Ready Preview, Comment Jan 5, 2026 7:15pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This change introduces bot ID protection infrastructure across the application by adding a new botid dependency and implementing bot verification utilities. A reusable bot protection pattern is applied to multiple API routes via enforceBotIdProtection and applyBotIdHeaders, with a client-side loader component integrated into the root layout. Additionally, the iframely route gains embeddability checking and iframe.ly API integration.

Changes

Cohort / File(s) Summary
Dependency Management
package.json
Added botid v1.5.10 to dependencies.
Bot ID Protection Infrastructure
src/common/utils/botIdProtection.ts
New utility module exporting enforceBotIdProtection(), applyBotIdHeaders(), botIdProtectedRoutes configuration, and related types. Handles bot verification via botid/server, header normalization, development mode bypass, and response header injection.
Bot ID Client Component & Layout
src/common/components/BotIdProtectionLoader.tsx, src/app/layout.tsx
New client component BotIdProtectionLoader renders BotIdClient with protected routes mapping; integrated into root layout head.
API Route: Frames
src/app/api/frames/route.ts
Added bot protection enforcement and header application. Introduced respond() helper to wrap JSON responses with bot headers in both GET and POST handlers.
API Route: Iframely
src/app/api/iframely/route.ts
Added bot protection flow; introduced checkEmbeddability(url) utility performing HEAD requests and inspecting X-Frame-Options and CSP headers; integrated getIframelyEmbed(url) for iframe.ly API calls; routes responses through unified respond() wrapper with directEmbed flag logic.
API Routes: Discovery, OpenGraph, RSS, Venice
src/app/api/miniapp-discovery/route.ts, src/app/api/opengraph/route.ts, src/app/api/rss/route.ts, src/app/api/venice/route.ts, src/app/api/venice/background/route.ts
Applied bot protection enforcement and respond() wrapper pattern across all handlers. Added query parameter parsing in miniapp-discovery (category, networks, timeWindow, limit, trending); enhanced RSS response with image and items fields; improved error handling consistency across all routes via centralized response wrapper.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Beware the bots that knock at your gates,
Protected now by botid's fate,
Headers wrapped in every request,
API routes put to the test,
Embeddability checked with care,
Security blooms everywhere! 🛡️

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding BotID safeguards across the codebase.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/api/iframely/route.ts (1)

59-73: Missing SSRF protection in embeddability check.

The checkEmbeddability function makes HEAD requests to user-supplied URLs without validating the scheme or blocking internal IPs. This could allow attackers to probe internal services.

🔎 Suggested fix: Add URL validation
 async function checkEmbeddability(url: string): Promise<boolean> {
   try {
+    // Validate URL scheme
+    const parsedUrl = new URL(url);
+    if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
+      return false;
+    }
+    
+    // Block internal URLs (reuse isInternalUrl from a shared utility)
+    if (isInternalUrl(url)) {
+      return false;
+    }
+
     // Make a HEAD request to check headers and cache the result
     const response = await fetch(url, {

Consider extracting the isInternalUrl helper from rss/route.ts into a shared utility module.

🧹 Nitpick comments (2)
src/common/utils/botIdProtection.ts (1)

31-53: Consider adding error handling for checkBotId failures.

The enforceBotIdProtection function does not handle potential errors from checkBotId. If the bot detection service fails or throws an error, the API route will crash rather than gracefully degrading.

🔎 Proposed enhancement with error handling
 export async function enforceBotIdProtection(
   request: BotIdRequestLike,
 ): Promise<BotIdVerificationResult | NextResponse> {
+  try {
     const verification = await checkBotId({
       advancedOptions: {
         headers: normalizeHeaders(request.headers),
       },
       developmentOptions: {
         isDevelopment,
       },
     });
 
     if (verification.isBot) {
       const response = NextResponse.json(
         { success: false, error: "Access denied" },
         { status: 403 },
       );
       applyBotIdHeaders(response, verification);
       return response;
     }
 
     return verification;
+  } catch (error) {
+    console.error("Bot ID verification failed:", error);
+    // In development, allow through; in production, you might want to block
+    if (isDevelopment) {
+      return { isBot: false, responseHeaders: {} } as BotIdVerificationResult;
+    }
+    // Or return a 500 error to be safe
+    return NextResponse.json(
+      { success: false, error: "Verification service unavailable" },
+      { status: 500 },
+    );
+  }
 }
src/app/api/miniapp-discovery/route.ts (1)

29-29: Type assertion with fallback works but is confusing.

The expression searchParams.get('timeWindow') as '...' || '7d' works because the cast happens first, but the null still evaluates as falsy. However, this doesn't validate that the input is actually one of the allowed values.

🔎 Suggested improvement for type safety
-    const timeWindow = searchParams.get('timeWindow') as '1h' | '6h' | '12h' | '24h' | '7d' || '7d';
+    const timeWindowParam = searchParams.get('timeWindow');
+    const validTimeWindows = ['1h', '6h', '12h', '24h', '7d'] as const;
+    const timeWindow = validTimeWindows.includes(timeWindowParam as any) 
+      ? (timeWindowParam as typeof validTimeWindows[number]) 
+      : '7d';
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b96e66a and 9884889.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • package.json
  • src/app/api/frames/route.ts
  • src/app/api/iframely/route.ts
  • src/app/api/miniapp-discovery/route.ts
  • src/app/api/opengraph/route.ts
  • src/app/api/rss/route.ts
  • src/app/api/venice/background/route.ts
  • src/app/api/venice/route.ts
  • src/app/layout.tsx
  • src/common/components/BotIdProtectionLoader.tsx
  • src/common/utils/botIdProtection.ts
🧰 Additional context used
🧬 Code graph analysis (9)
src/app/api/venice/background/route.ts (2)
src/app/api/venice/config.ts (1)
  • VENICE_API_KEY (7-7)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
src/app/api/miniapp-discovery/route.ts (2)
src/app/api/iframely/route.ts (1)
  • GET (9-57)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
src/common/components/BotIdProtectionLoader.tsx (1)
src/common/utils/botIdProtection.ts (1)
  • botIdProtectedRoutes (19-29)
src/app/api/opengraph/route.ts (1)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
src/app/api/rss/route.ts (1)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
src/app/api/iframely/route.ts (1)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
src/app/api/venice/route.ts (2)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
src/app/api/venice/config.ts (1)
  • VENICE_API_KEY (7-7)
src/app/layout.tsx (1)
src/common/components/BotIdProtectionLoader.tsx (1)
  • BotIdProtectionLoader (7-9)
src/app/api/frames/route.ts (1)
src/common/utils/botIdProtection.ts (2)
  • enforceBotIdProtection (31-53)
  • applyBotIdHeaders (55-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (19)
src/common/components/BotIdProtectionLoader.tsx (1)

1-9: LGTM!

The component correctly implements client-side bot protection by rendering BotIdClient with the configured protected routes. The "use client" directive is appropriate for this client-side component.

src/common/utils/botIdProtection.ts (3)

5-29: LGTM!

The type definitions and protected routes configuration are well-structured. The inclusion of both GET and POST methods for certain routes ensures comprehensive protection.


55-79: LGTM!

The header application logic is defensive and correctly handles the three types of header operations (replace, add, append). The early returns prevent errors when response headers are missing.


81-104: LGTM!

The header normalization logic correctly handles HTTP header semantics, including case-insensitivity (Line 85) and multiple values for the same header name (Lines 88-100).

src/app/api/venice/background/route.ts (2)

12-22: LGTM!

The bot protection integration follows the correct pattern: early verification with immediate return if blocked, and a reusable respond wrapper to ensure bot headers are consistently applied to all responses.


25-89: Consistent response handling.

All response paths correctly use the respond wrapper, ensuring bot protection headers are applied consistently across success and error responses.

package.json (1)

96-96: [email protected] is valid and secure.

Version 1.5.10 of the botid package exists on npm and has no known vulnerabilities or deprecation warnings.

src/app/layout.tsx (1)

131-131: BotIdClient placement in is correct per package documentation.

The BotIdProtectionLoader component is correctly placed inside the document <head>. According to the botid package documentation for Next.js, the <BotIdClient /> component should be mounted in the root layout's <head> to ensure client-side bot protection runs early during page initialization.

src/app/api/frames/route.ts (3)

3-6: Bot protection integration looks correct.

The bot ID protection is properly integrated at the start of the GET handler, with early return on bot detection and consistent header application via the respond wrapper. The pattern aligns with the shared utilities in botIdProtection.ts.

Also applies to: 238-248


331-341: POST handler bot protection correctly mirrors GET.

The bot protection flow and respond wrapper are consistently applied in the POST handler, ensuring all responses include bot ID headers.


16-56: SSRF protection preserved.

The existing isInternalUrl function continues to block internal IP ranges and localhost. The error responses now correctly route through the respond wrapper for consistent header application.

src/app/api/rss/route.ts (1)

4-7: Bot protection integration is correct.

The pattern is consistently applied: enforceBotIdProtection at the start, early return on bot detection, and all responses routed through the respond wrapper. The SSRF protection logic is preserved and more robust here (using node:net isIP).

Also applies to: 77-87

src/app/api/opengraph/route.ts (1)

3-6: Bot protection integration is correct.

The pattern is consistently applied with early return on bot detection and all responses routed through respond.

Also applies to: 9-19

src/app/api/venice/route.ts (2)

14-17: Bot protection integration is correct.

The POST handler properly enforces bot protection and routes all responses through the respond wrapper. Since this route only calls known external APIs (Venice, Neynar) rather than user-supplied URLs, SSRF protection isn't needed here.

Also applies to: 36-46


171-179: Error details exposure is acceptable but verify for production.

The error details are now included in the response. While helpful for debugging, ensure sensitive internal errors from the Venice API don't leak to clients in production. The current implementation looks reasonable for known API errors.

src/app/api/miniapp-discovery/route.ts (2)

3-6: Bot protection integration is correct for GET handler.

The pattern is consistently applied with early return on bot detection and the respond wrapper for all responses.

Also applies to: 9-19


94-104: POST handler bot protection is correctly integrated.

All action branches (refresh, search, trending, discover, default) properly use the respond wrapper.

src/app/api/iframely/route.ts (2)

2-5: Bot protection integration is correct.

The pattern is consistently applied with early return on bot detection and all responses routed through respond.

Also applies to: 10-20


120-125: Verify API key exposure is intentional.

NEXT_PUBLIC_IFRAMELY_API_KEY uses the NEXT_PUBLIC_ prefix, which exposes it to the client-side bundle. Since this code runs server-side in a route handler, consider using a non-prefixed env var (e.g., IFRAMELY_API_KEY) to keep the key server-only, unless Iframely specifically requires client-side exposure.

Comment on lines 45 to 57
if (parsedUrl.protocol !== "https:") {
// Only allow https URLs for security; reject http and other protocols
return NextResponse.json({
title: parsedUrl.hostname || url,
description: null,
image: null,
siteName: parsedUrl.hostname || url,
url,
error: `Only https URLs are allowed. Unsupported URL protocol: ${parsedUrl.protocol}`
});
return respond(
{
title: parsedUrl.hostname || url,
description: null,
image: null,
siteName: parsedUrl.hostname || url,
url,
error: `Only https URLs are allowed. Unsupported URL protocol: ${parsedUrl.protocol}`,
},
{ status: 400 },
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider adding SSRF protection.

This route only validates the https: protocol but doesn't block internal IP ranges or localhost like frames/route.ts and rss/route.ts do. An attacker could potentially request internal HTTPS services (e.g., https://10.0.0.1/... or https://localhost:8443/...).

🔎 Suggested fix: Add isInternalUrl check

You could reuse or extract the isInternalUrl helper from rss/route.ts into a shared utility and apply it here:

  if (parsedUrl.protocol !== "https:") {
    return respond(
      {
        title: parsedUrl.hostname || url,
        description: null,
        image: null,
        siteName: parsedUrl.hostname || url,
        url,
        error: `Only https URLs are allowed. Unsupported URL protocol: ${parsedUrl.protocol}`,
      },
      { status: 400 },
    );
  }

+ if (isInternalUrl(url)) {
+   return respond(
+     { error: "Invalid URL. Internal URLs are not allowed." },
+     { status: 400 },
+   );
+ }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/api/opengraph/route.ts around lines 45 to 57, the handler only
enforces the https: protocol but does not block internal/localhost addresses,
leaving it open to SSRF; import or move the existing isInternalUrl helper (or
implement equivalent) used by frames/route.ts and rss/route.ts, run it after
parsing the URL and before fetching, and if it returns true respond with a 400
(or same error shape) indicating internal URLs are not allowed; ensure the
helper checks localhost, loopback, private RFC1918 ranges and IPv6 equivalents
and use the same error message format as the surrounding code for consistency.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds bot protection using the [email protected] package to secure API endpoints from automated bot traffic. The implementation includes both server-side protection via middleware and client-side protection through a global component loader.

Key Changes

  • Installed [email protected] package and integrated BotID protection for 7 API endpoints
  • Created centralized bot protection utilities with header normalization and response handling
  • Added client-side BotIdProtectionLoader component to enforce protection globally

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
package.json Added [email protected] dependency
yarn.lock Locked botid package version
src/common/utils/botIdProtection.ts Implements core bot detection logic with header normalization and response utilities
src/common/components/BotIdProtectionLoader.tsx Client-side component for global route protection
src/app/layout.tsx Integrated BotIdProtectionLoader into application layout
src/app/api/venice/route.ts Added bot protection and improved error handling with consistent JSON responses
src/app/api/venice/background/route.ts Added bot protection and standardized error responses
src/app/api/rss/route.ts Protected endpoint with bot detection and applied BotID headers to responses
src/app/api/opengraph/route.ts Added bot protection and improved error responses with status codes
src/app/api/miniapp-discovery/route.ts Protected both GET and POST methods with bot detection
src/app/api/iframely/route.ts Added bot protection with consistent response pattern
src/app/api/frames/route.ts Protected both GET and POST handlers with bot detection

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to +53
title: parsedUrl.hostname || url,
description: null,
image: null,
siteName: parsedUrl.hostname || url,
url,
error: `Only https URLs are allowed. Unsupported URL protocol: ${parsedUrl.protocol}`,
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error response includes the full parsed URL in the response body when the protocol is not https. This could potentially expose internal server details or unexpected data to clients. Consider only returning a sanitized error message without the parsed URL details.

Suggested change
title: parsedUrl.hostname || url,
description: null,
image: null,
siteName: parsedUrl.hostname || url,
url,
error: `Only https URLs are allowed. Unsupported URL protocol: ${parsedUrl.protocol}`,
title: null,
description: null,
image: null,
siteName: null,
url: null,
error: "Only https URLs are allowed.",

Copilot uses AI. Check for mistakes.

if (verification.isBot) {
const response = NextResponse.json(
{ success: false, error: "Access denied" },
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error response when a bot is detected returns a generic "Access denied" message. Consider adding more context in the error message to help legitimate users understand why their request was denied, such as "Request blocked by bot protection" or providing a reference ID for troubleshooting.

Suggested change
{ success: false, error: "Access denied" },
{ success: false, error: "Request blocked by bot protection" },

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +89
return respond(
{ error: "All models failed", details: lastError },
{ status: 500 },
);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error response includes the lastError details which may contain sensitive information such as API keys, internal server paths, or stack traces if it's an Error object. This could leak internal implementation details to potential attackers. Consider sanitizing or omitting the details field in production, or only including safe error information.

Copilot uses AI. Check for mistakes.
{validatedUiStylesheet && (
<link rel="stylesheet" href={validatedUiStylesheet} />
)}
<BotIdProtectionLoader />
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BotIdProtectionLoader client component is placed inside the head tag, which is unusual and may cause hydration issues or unexpected behavior in React. Client-side components are typically placed in the body tag. According to React best practices, the head should contain metadata and static resources. Consider moving this component to the body tag, perhaps before the SpeedInsights component or within the Providers wrapper.

Copilot uses AI. Check for mistakes.
@Jhonattan2121 Jhonattan2121 marked this pull request as draft January 4, 2026 19:21
@Jhonattan2121
Copy link
Collaborator Author

@willyogo I did what the issue asked for. Is there anything else that needs to be tested? I ran tests and implemented the changes. If there’s anything else, please let me know.

…ting bundle analyzer with the BotID functionality.
@willyogo
Copy link
Member

willyogo commented Jan 5, 2026

@Jhonattan2121 good q. would like to test this and confirm that it both works and also does not break anything / negatively impact performance before merging. can you try and test both of the above and report back with results?

…nt by moving it directly into the body and removing it from the head section.
@Jhonattan2121
Copy link
Collaborator Author

BotID Protection - Test Report

Executive Summary

This report documents testing performed on PR #1654 implementing Vercel BotID protection. Testing confirms the implementation is working as intended, successfully blocking automated requests while allowing legitimate browser traffic.


Test Methodology

Environments Tested

Environment URL BotID Status
Preview (PR #1654) https://space-system-k5bfbnfoc-blankdotspace.vercel.app Active
Production https://www.nounspace.com Not deployed

Test Approaches

  1. Automated Browser Testing - Playwright-based requests from within browser context
  2. Manual Browser Testing - Chrome DevTools Network tab validation
  3. Baseline Comparison - Production vs Preview behavior comparison

Preview Environment (BotID Active)

Endpoint: GET /api/rss?url=https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml
Status: 403
Response: {"success": false, "error": "Access denied"}

Endpoint: GET /api/iframely?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ
Status: 403
Response: {"success": false, "error": "Access denied"}

Endpoint: GET /api/miniapp-discovery
Status: 403
Response: {"success": false, "error": "Access denied"}

Endpoint: GET /api/frames?url=https://www.nytimes.com
Status: 403
Response: {"success": false, "error": "Access denied"}

Endpoint: GET /api/opengraph?url=https://www.nytimes.com
Status: 500
Note: Internal server error unrelated to BotID (occurs in both environments)

Result: 4/5 protected endpoints correctly blocked automated requests.

Production Environment (No BotID)

Endpoint: GET /api/rss?url=https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml
Status: 200
Response: Valid RSS data returned

Endpoint: GET /api/iframely?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ
Status: 200
Response: Valid iFramely data returned

Endpoint: GET /api/miniapp-discovery
Status: 200
Response: Valid discovery data returned

Endpoint: GET /api/frames?url=https://www.nytimes.com
Status: 200
Response: Valid frame data returned

Result: All endpoints accessible without protection, confirming BotID is not yet deployed in production.

Manual Browser Testing

Test: Manual fetch via Chrome DevTools Console

fetch('/api/rss?url=https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml')

Result:

  • Status: 200 OK
  • Response: Valid JSON with NYT RSS feed data
  • Content-Type: application/json

Conclusion: Legitimate browser requests are correctly allowed through BotID protection.


Protected Endpoints Configuration

The following endpoints are protected per src/common/utils/botIdProtection.ts:

  • /api/miniapp-discovery (GET, POST)
  • /api/opengraph (GET)
  • /api/frames (GET, POST)
  • /api/iframely (GET)
  • /api/rss (GET)
  • /api/venice (POST)
  • /api/venice/background (POST)

All tested endpoints (except /api/venice endpoints) correctly enforce BotID protection in the preview environment.


Implementation Review

Code Changes

  1. Server-side protection - src/common/utils/botIdProtection.ts

    • enforceBotIdProtection() returns 403 response for detected bots
    • Applied to all configured protected routes
  2. Client-side component - src/common/components/BotIdProtectionLoader.tsx

    • Loads BotID client SDK
    • Placed in <body> tag in src/app/layout.tsx
  3. Next.js configuration - next.config.mjs

    • withBotId() wrapper added for proxy rewrites
  4. API route integration

    • Each protected route calls enforceBotIdProtection(request)
    • Returns 403 with applyBotIdHeaders() for bot traffic
    • Passes through legitimate requests

Verification Against Documentation

Implementation follows Vercel BotID documentation requirements:

  • Package installed (botid)
  • Client component mounted
  • Server-side verification implemented
  • Protected routes configured
  • Next.js config updated with wrapper

@willyogo
Copy link
Member

willyogo commented Jan 5, 2026

thanks for the test report @Jhonattan2121!

overall looks good. were you able to tell if there are any downsides to enabling this? ie. is response time slower for these endpoints when bot protection is enabled?

i'm wondering if it's necessary or net-beneficial to implement this on all of the endpoints vs. just the frontend.

@Jhonattan2121
Copy link
Collaborator Author

@willyogo
Based on the tests I ran and what I observed, it doesn’t become heavy
it keeps the same performance and response time, so it can be used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants