feat: secure copilot token generation via server action
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
⚠️ No Changeset found
Latest commit: 95eab78367f1f695561928cc8fad29bef996d449
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
The latest updates on your projects. Learn more about Vercel for GitHub.
| Project | Deployment | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| agents-manage-api | Preview | Comment | Nov 25, 2025 6:26am | |
| agents-manage-ui | Preview | Comment | Nov 25, 2025 6:26am | |
| agents-run-api | Preview | Comment | Nov 25, 2025 6:26am | |
| agents-test | Nov 25, 2025 6:26am |
1 Skipped Deployment
| Project | Deployment | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| agents-docs | Nov 25, 2025 6:26am |
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_SECRETfrom 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
-
Consistent with existing patterns: The server action follows the established
ActionResult<T>pattern used throughout the codebase - Proper separation of concerns: Clean division between server action (auth/token fetching) and client hook (state management/auto-refresh)
-
Reuses existing infrastructure: Leverages the existing
/playground/tokenendpoint 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:
- Component mounts and triggers initial fetch
- Auto-refresh timer fires simultaneously
- 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
-
Console logging (
use-copilot-token.ts:53): Consider using a proper logging library or removing console.logs for production -
Loading state optimization (
copilot-chat.tsx:79-81): The component rendersnullduring loading, which could cause layout shift. Consider a loading spinner.
📋 Missing Considerations
- Rate limiting: No rate limiting on token refresh requests
- Offline handling: No strategy for handling offline scenarios
- 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:
- HIGH: Sanitize error messages to prevent information leakage
- MEDIUM: Add request deduplication and improve timer cleanup
- 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.