-
Notifications
You must be signed in to change notification settings - Fork 0
Add get_user tool to codebase #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,50 @@ import { getSystemPrompt } from '../domains'; | |
|
|
||
| const LITELLM_SERVER_URL = process.env.LITELLM_SERVER_URL || 'http://localhost:4000'; | ||
|
|
||
| // Tool definitions for AI function calling | ||
| const TOOLS = [ | ||
| { | ||
| type: 'function', | ||
| function: { | ||
| name: 'get_user', | ||
| description: 'Get information about the current authenticated user including their ID, role, and token details', | ||
| parameters: { | ||
| type: 'object', | ||
| properties: {}, | ||
| required: [] | ||
| } | ||
| } | ||
| } | ||
| ]; | ||
|
|
||
| // Execute tool calls and return results | ||
| function executeToolCall(toolName: string, args: any, req: Request): string { | ||
| switch (toolName) { | ||
| case 'get_user': | ||
| if (req.user) { | ||
| return JSON.stringify({ | ||
| userId: req.user.sub, | ||
| role: req.user.role || 'unknown', | ||
| issuer: req.user.iss, | ||
| audience: req.user.aud, | ||
| issuedAt: req.user.iat ? new Date(req.user.iat * 1000).toISOString() : null, | ||
| expiresAt: req.user.exp ? new Date(req.user.exp * 1000).toISOString() : null, | ||
| scope: req.user.scope || null | ||
| }); | ||
|
Comment on lines
+28
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MediumThe 💡 Suggested FixConsider anonymizing user data before sending it to the LLM. Instead of real user IDs, use session-scoped hashes and generalized role categories: case 'get_user':
if (req.user) {
return JSON.stringify({
// Use session-scoped anonymous ID instead of actual user ID
sessionId: generateSessionHash(req.user.sub, req.sessionID),
// Generalize role to broader categories
roleCategory: categorizeRole(req.user.role),
authenticated: true
});
} else {
return JSON.stringify({
error: 'No authenticated user',
message: 'This endpoint requires authentication to retrieve user information'
});
}
// Helper functions
function generateSessionHash(userId: string, sessionId: string): string {
const crypto = require('crypto');
return crypto.createHash('sha256')
.update(`${userId}-${sessionId}`)
.digest('hex')
.substring(0, 16);
}
function categorizeRole(role: string | undefined): string {
const adminRoles = ['admin', 'superadmin', 'owner'];
const powerRoles = ['moderator', 'editor', 'contributor'];
if (!role) return 'user';
if (adminRoles.includes(role.toLowerCase())) return 'admin';
if (powerRoles.includes(role.toLowerCase())) return 'power-user';
return 'user';
}Alternatively, if user context isn't essential for the chat functionality, consider removing the tool entirely to avoid any PII exposure. 🤖 AI Agent PromptThe code at Your task is to investigate and implement a privacy-preserving solution:
The goal is to minimize PII exposure to external parties while maintaining the functionality that users need.
Comment on lines
+28
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium The 💡 Suggested FixConsider minimizing the data sent to the LLM. If per-user personalization isn't required, send only the role instead of the user ID: case 'get_user':
if (req.user) {
return JSON.stringify({
role: req.user.role || 'unknown',
authenticated: true
});
} else {
return JSON.stringify({
error: 'No authenticated user',
message: 'This endpoint requires authentication to retrieve user information'
});
}Alternatively, if you need per-user context, hash the user ID to reduce PII exposure: import { createHash } from 'crypto';
function hashUserId(userId: string): string {
return createHash('sha256').update(userId).digest('hex').substring(0, 16);
}
// In the get_user case:
return JSON.stringify({
userId: hashUserId(req.user.sub),
role: req.user.role || 'unknown',
// ... other fields
});🤖 AI Agent PromptThe code at Investigate the following to determine the appropriate fix:
Start by examining the system prompt and chat context to understand what information the LLM actually needs to provide useful responses. |
||
| } else { | ||
| return JSON.stringify({ | ||
| error: 'No authenticated user', | ||
| message: 'This endpoint requires authentication to retrieve user information' | ||
| }); | ||
| } | ||
| default: | ||
| return JSON.stringify({ | ||
| error: 'Unknown tool', | ||
| message: `Tool '${toolName}' is not available` | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // Map fish names to internal security levels | ||
| const FISH_TO_LEVEL: Record<string, 'insecure' | 'secure'> = { | ||
| minnow: 'insecure', | ||
|
|
@@ -116,34 +160,88 @@ export async function chatHandler(req: Request, res: Response): Promise<void> { | |
|
|
||
| // Prepare LiteLLM request | ||
| const litellmRequest: any = { | ||
| messages: messages | ||
| messages: messages, | ||
| tools: TOOLS, | ||
| tool_choice: 'auto' | ||
| }; | ||
|
|
||
| // Add model if provided | ||
| if (model) { | ||
| litellmRequest.model = model; | ||
| } | ||
|
|
||
| // Forward request to LiteLLM server | ||
| const response = await fetch(`${LITELLM_SERVER_URL}/v1/chat/completions`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(litellmRequest), | ||
| }); | ||
| // Tool call loop - continue until we get a final response | ||
| const MAX_TOOL_ITERATIONS = 10; | ||
| let iteration = 0; | ||
|
|
||
| if (!response.ok) { | ||
| const errorText = await response.text(); | ||
| res.status(response.status).json({ | ||
| error: 'LiteLLM server error', | ||
| message: errorText | ||
| while (iteration < MAX_TOOL_ITERATIONS) { | ||
| iteration++; | ||
|
|
||
| // Forward request to LiteLLM server | ||
| const response = await fetch(`${LITELLM_SERVER_URL}/v1/chat/completions`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(litellmRequest), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const errorText = await response.text(); | ||
| res.status(response.status).json({ | ||
| error: 'LiteLLM server error', | ||
| message: errorText | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
| const assistantMessage = data.choices?.[0]?.message; | ||
|
|
||
| // Check if the model wants to call tools | ||
| if (assistantMessage?.tool_calls && assistantMessage.tool_calls.length > 0) { | ||
| // Add the assistant's message with tool calls to the conversation | ||
| litellmRequest.messages.push({ | ||
| role: 'assistant', | ||
| content: assistantMessage.content || null, | ||
| tool_calls: assistantMessage.tool_calls | ||
| }); | ||
|
|
||
| // Execute each tool call and add results | ||
| for (const toolCall of assistantMessage.tool_calls) { | ||
| const toolName = toolCall.function.name; | ||
| let toolArgs = {}; | ||
|
|
||
| try { | ||
| toolArgs = JSON.parse(toolCall.function.arguments || '{}'); | ||
| } catch { | ||
| // If parsing fails, use empty args | ||
| } | ||
|
|
||
| const toolResult = executeToolCall(toolName, toolArgs, req); | ||
|
|
||
| // Add tool result to messages | ||
| litellmRequest.messages.push({ | ||
| role: 'tool', | ||
| tool_call_id: toolCall.id, | ||
| content: toolResult | ||
| }); | ||
| } | ||
|
|
||
| // Continue the loop to get the model's response to tool results | ||
| continue; | ||
| } | ||
|
|
||
| // No tool calls - return the final response | ||
| res.json(data); | ||
| return; | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
| res.json(data); | ||
| // If we hit max iterations, return an error | ||
| res.status(500).json({ | ||
| error: 'Tool call limit exceeded', | ||
| message: `Maximum tool call iterations (${MAX_TOOL_ITERATIONS}) reached` | ||
| }); | ||
| } catch (error) { | ||
| console.error('Error in chat handler:', error); | ||
| res.status(500).json({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 MediumThe get_user tool sends user PII (user ID, role, token issuer, audience, timestamps, and scope) to the external LiteLLM service. While this appears to be a single-user context where users access only their own data, the token metadata fields (issuer, audience, issuedAt, expiresAt, scope) may be unnecessary for typical chat personalization and increase the PII footprint sent to the LLM provider.
💡 Suggested Fix
Apply data minimization by limiting the tool to essential fields only:
Additionally, ensure your LiteLLM deployment has appropriate data protection agreements in place, and consider updating your privacy policy to disclose that user data may be sent to the LLM provider during chat interactions.
🤖 AI Agent Prompt
The get_user tool at
src/routes/chat.ts:28-36exposes user PII including JWT token metadata (issuer, audience, timestamps, scope) to an external LiteLLM service. Investigate whether all these fields are necessary for the chat personalization use case. Check if there are any privacy policy or user consent mechanisms that cover this data sharing. Consider whether data minimization would be appropriate - typically only userId and role are needed for personalization. Also investigate the LiteLLM deployment model (self-hosted vs. cloud) and whether data processing agreements are in place. If this is a self-hosted deployment, the risk may be lower. Look for any application-wide patterns for handling PII in LLM contexts.