Skip to content

Conversation

@amikofalvy
Copy link
Collaborator

@amikofalvy amikofalvy commented Nov 25, 2025

Summary

Replaces the exposed bypass secret in CopilotChat with a secure server action that fetches temporary JWT tokens, with proper retry behavior and error handling.

Changes

New Files

  • agents-manage-ui/src/lib/actions/copilot-token.ts - Server action that calls manage API's playground token endpoint
  • agents-manage-ui/src/hooks/use-copilot-token.ts - Client hook with:
    • Exponential backoff retry (3 attempts: 1s, 2s, 4s delays)
    • Skips retries for configuration errors
    • Auto-refresh 5 minutes before token expiry
    • Mounted state tracking to prevent memory leaks

Modified Files

  • agents-manage-ui/src/components/agent/copilot/copilot-chat.tsx - Uses new hook with proper error states:
    • Loading state with retry count indicator
    • Error alert with retry button when token fetch fails
    • Graceful degradation instead of silent failure

Visual States

State Display
Initial load Loading... with spinner
Retrying Reconnecting (attempt 1/3)... with spinner
Failed Alert with error message and Try Again button
Success Normal copilot chat sidebar

Configuration

Requires new environment variable on manage-ui server:

INKEEP_COPILOT_API_KEY=<api-key-with-access-to-copilot-tenant>

Security Benefits

  • Bypass secret no longer exposed to client
  • Tokens are short-lived (1 hour) and scoped to copilot agent
  • Auto-refresh 5 minutes before expiry
  • Retry logic handles transient network failures

- Add getCopilotTokenAction server action to fetch temp tokens from manage API
- Add useCopilotToken hook with auto-refresh before expiry
- Update CopilotChat to use server action instead of exposing bypass secret

Requires INKEEP_COPILOT_API_KEY env var on manage-ui server
@changeset-bot
Copy link

changeset-bot bot commented Nov 25, 2025

⚠️ No Changeset found

Latest commit: 95eab78

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Nov 25, 2025

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

Project Deployment Preview Comments Updated (UTC)
agents-manage-api Ready Ready Preview Comment Nov 25, 2025 6:26am
agents-manage-ui Ready Ready Preview Comment Nov 25, 2025 6:26am
agents-run-api Ready Ready Preview Comment Nov 25, 2025 6:26am
agents-test Canceled Canceled Nov 25, 2025 6:26am
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
agents-docs Skipped Skipped Nov 25, 2025 6:26am

@claude
Copy link

claude bot commented Nov 25, 2025

Claude finished @amikofalvy's task —— View job


🔍 TypeScript Staff Engineer Code Review

I've completed a comprehensive architectural review of the secure copilot token implementation. This PR represents a significant security improvement by replacing the exposed bypass secret with a proper JWT-based token system.

Excellent Security Improvements

The core architectural change is sound and addresses the fundamental security issue:

  • Eliminates client-side secret exposure: Removes PUBLIC_INKEEP_AGENTS_RUN_API_BYPASS_SECRET from the browser
  • Short-lived tokens: 1-hour expiry with auto-refresh provides good security posture
  • Proper scoping: Tokens are scoped to specific tenant/project/agent combinations

🎯 Architecture & Patterns

Strengths

  1. Consistent with existing patterns: The server action follows the established ActionResult<T> pattern used throughout the codebase
  2. Proper separation of concerns: Clean division between server action (auth/token fetching) and client hook (state management/auto-refresh)
  3. Reuses existing infrastructure: Leverages the existing /playground/token endpoint that's already well-tested

Areas for Consideration

🔒 Security & Error Handling

HIGH PRIORITY - Error Information Leakage (copilot-token.ts:64-76)

let errorMessage = 'Failed to fetch copilot token';
try {
  const errorData = await response.json();
  errorMessage = errorData?.error?.message || errorData?.message || errorMessage;
} catch {
  // Ignore JSON parse errors
}

Issue: This exposes potentially sensitive API error details to the client. Consider sanitizing error messages or using generic errors for security.

Recommendation:

// Log the actual error server-side for debugging
console.error('Copilot token API error:', errorData);
// Return generic error to client
return {
  success: false,
  error: 'Authentication service temporarily unavailable',
  code: 'api_error',
};

Performance & Race Conditions

MEDIUM PRIORITY - Multiple Simultaneous Requests (use-copilot-token.ts)
The current implementation could trigger multiple simultaneous token requests if:

  1. Component mounts and triggers initial fetch
  2. Auto-refresh timer fires simultaneously
  3. Manual refresh is called

Recommendation: Add request deduplication:

const [isRefreshing, setIsRefreshing] = useState(false);

const fetchToken = useCallback(async () => {
  if (isRefreshing) return; // Prevent concurrent requests
  
  try {
    setIsRefreshing(true);
    setIsLoading(true);
    // ... existing logic
  } finally {
    setIsRefreshing(false);
    setIsLoading(false);
  }
}, [isRefreshing]);

MEDIUM PRIORITY - Timer Cleanup Edge Case (use-copilot-token.ts:52-57)
If expiresAt changes rapidly (edge case), old timers might not be cleaned up properly due to closure scope.

Recommendation:

useEffect(() => {
  if (!expiresAt) return;
  
  const expiryTime = new Date(expiresAt).getTime();
  const now = Date.now();
  const timeUntilExpiry = expiryTime - now;
  
  // Don't set timer if token is already expired or expires very soon
  if (timeUntilExpiry <= 5 * 60 * 1000) {
    fetchToken();
    return;
  }
  
  const refreshTime = timeUntilExpiry - 5 * 60 * 1000;
  const timer = setTimeout(() => {
    console.log('Auto-refreshing copilot token before expiry...');
    fetchToken();
  }, refreshTime);

  return () => clearTimeout(timer);
}, [expiresAt, fetchToken]);

🏗️ Type Safety & Architecture

LOW PRIORITY - Type Duplication
The ActionResult<T> type is defined in both copilot-token.ts and actions/types.ts.

Recommendation: Import from the shared types file:

// copilot-token.ts
import type { ActionResult } from './types';
// Remove duplicate type definition

LOW PRIORITY - API Response Type Safety (copilot-token.ts:78-84)
The response parsing assumes the API shape but doesn't validate it.

Recommendation: Consider using a schema validation library (like zod) or at minimum add runtime checks:

const data = await response.json();
if (!data.apiKey || !data.expiresAt) {
  throw new Error('Invalid API response format');
}

🔧 Configuration & Error Boundaries

MEDIUM PRIORITY - Environment Variable Validation
The server action validates individual env vars but doesn't provide helpful context about which specific variable is missing.

Current:

if (!copilotTenantId || !copilotProjectId || !copilotAgentId) {
  return {
    success: false,
    error: 'Copilot tenant, project, or agent ID is not configured',
    code: 'configuration_error',
  };
}

Recommendation: More specific error messages for easier debugging:

const missingVars = [];
if (!copilotTenantId) missingVars.push('PUBLIC_INKEEP_COPILOT_TENANT_ID');
if (!copilotProjectId) missingVars.push('PUBLIC_INKEEP_COPILOT_PROJECT_ID'); 
if (!copilotAgentId) missingVars.push('PUBLIC_INKEEP_COPILOT_AGENT_ID');

if (missingVars.length > 0) {
  return {
    success: false,
    error: `Missing environment variables: ${missingVars.join(', ')}`,
    code: 'configuration_error',
  };
}

🎯 Minor Improvements

  1. Console logging (use-copilot-token.ts:53): Consider using a proper logging library or removing console.logs for production

  2. Loading state optimization (copilot-chat.tsx:79-81): The component renders null during loading, which could cause layout shift. Consider a loading spinner.

📋 Missing Considerations

  1. Rate limiting: No rate limiting on token refresh requests
  2. Offline handling: No strategy for handling offline scenarios
  3. Token validation: No client-side token expiry validation before making requests

🏁 Overall Assessment

This is a well-architected security improvement that follows established patterns and significantly enhances the security posture. The implementation is clean, consistent, and addresses the core security concern effectively.

Recommended Priority for Fixes:

  1. HIGH: Sanitize error messages to prevent information leakage
  2. MEDIUM: Add request deduplication and improve timer cleanup
  3. LOW: Fix type imports and add response validation

The changes demonstrate solid TypeScript practices and good understanding of React hooks patterns. The security benefits far outweigh the minor areas for improvement identified above.


- Add exponential backoff retry (3 attempts) for transient errors
- Skip retries for configuration errors
- Show loading state with retry count indicator
- Show error alert with 'Try Again' button when all retries fail
- Track mounted state to prevent state updates on unmounted component
@github-actions
Copy link
Contributor

This pull request has been automatically marked as stale because it has not had recent activity.
It will be closed in 7 days if no further activity occurs.

If this PR is still relevant:

  • Rebase it on the latest main branch
  • Add a comment explaining its current status
  • Request a review if it's ready

Thank you for your contributions!

@github-actions github-actions bot added the stale label Dec 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants