Skip to content

Shared module-level history array leaks all users' AI questions into each other's context #85

@anshul23102

Description

@anshul23102

Summary

commands/ai/aiassistant.js declares history as a module-level array that persists across all invocations of the /aiassistant ask command. Every user's question and the AI's reply are appended to this single shared array. As a result:

  • User B's request includes User A's question and answer as conversation history, so the AI model sees — and can reference — another user's private input.
  • The array grows without bound, eventually exceeding Gemini's context window or causing high memory consumption on the bot process.

Vulnerable code

// module scope — shared across every user, every guild
const history = [];

// inside execute():
history.push({ role: "user", parts: [{ text: question }] });
const response = await ai.models.generateContent({
  model: "gemini-2.5-flash",
  contents: history,   // every previous user's Q&A is included
  ...
});
history.push({ role: "model", parts: [{ text: response.text }] });

Impact

  • Cross-user data exposure: User B receives an AI response influenced by User A's question. If User A asked about something personal or sensitive, that information leaks into User B's session through the shared context window.
  • Unbounded memory growth: The array is never trimmed. After enough usage the process will run out of memory or hit Gemini's token limit, causing errors for all users.
  • Cross-guild leakage: The same array is shared across all servers the bot is in. Users in different guilds see each other's conversation history.

Suggested Fix

Maintain per-user (or per-user-per-guild) history stored in the database rather than a module-level variable. A simple in-memory approach would key by userId:

// Remove module-level history = []

const userHistories = new Map(); // temporary in-process store; use DB for persistence

async execute(interaction, client) {
  const key = `${interaction.guild.id}:${interaction.user.id}`;
  const history = userHistories.get(key) ?? [];

  history.push({ role: "user", parts: [{ text: question }] });
  // keep only the last N exchanges to bound token usage
  const trimmed = history.slice(-10);
  
  const response = await ai.models.generateContent({
    model: "gemini-2.5-flash",
    contents: trimmed,
    ...
  });

  trimmed.push({ role: "model", parts: [{ text: response.text }] });
  userHistories.set(key, trimmed);
  ...
}

For production, persist history in MongoDB (which the bot already uses) and load it at the start of each interaction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions