Skip to content

fix(core): Resolve TOCTOU Data Loss Race Condition in MemoryTool Execution#20752

Closed
KumarADITHYA123 wants to merge 1 commit intogoogle-gemini:mainfrom
KumarADITHYA123:fix-core-toctou-memorytool
Closed

fix(core): Resolve TOCTOU Data Loss Race Condition in MemoryTool Execution#20752
KumarADITHYA123 wants to merge 1 commit intogoogle-gemini:mainfrom
KumarADITHYA123:fix-core-toctou-memorytool

Conversation

@KumarADITHYA123
Copy link
Copy Markdown
Contributor

@KumarADITHYA123 KumarADITHYA123 commented Mar 1, 2026

Fixes #20746

ARCHITECTURAL OVERVIEW
This pull request addresses a critical Time-of-Check to Time-of-Use (TOCTOU) vulnerability within the MemoryTool lifecycle.

Prior to this patch, the MemoryTool eagerly read the GEMINI.md file during the confirmation and diff-generation phase, caching the proposed overwrite state. If the user confirmed the edit, the tool blindly wrote the cached string to disk in the execution phase.

If the user modified GEMINI.md directly during the confirmation prompt, or if a multi-agent parallel execution modified the file context concurrently, the MemoryTool would forcefully overwrite and vaporize those intermediate changes, leading to silent data loss.

THE FIX
The solution introduces a strict re-validation layer at the execution boundary.

  1. State Preservation: I now retain the original content state captured during the initial read.
  2. Dynamic Re-Evaluation: Upon execution, I re-read the live file from disk.
  3. Conflict Resolution:
    A. If the file has mutated and the user did not manually edit the proposed memory chunk, I seamlessly recompute the diff against the newly discovered state and apply it safely.
    B. If the user injected a manual string edit during the prompt and the backing file has changed, I gracefully abort the operation to prevent a blind overwrite, prompting the user to resolve the collision safely.

TEST COVERAGE
Two novel test cases have been added to memoryTool.test.ts to explicitly simulate the TOCTOU concurrency race and verify the dynamic re-evaluation boundary.

I am happy to adjust the conflict resolution strategy if the maintainers prefer an alternative approach to handling user-modified collisions. Thank you for your time reviewing this architectural patch.

@KumarADITHYA123 KumarADITHYA123 requested a review from a team as a code owner March 1, 2026 12:06
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the robustness of the MemoryTool by addressing a critical Time-of-Check to Time-of-Use (TOCTOU) race condition. It prevents potential data loss that could occur if the memory file (GEMINI.md) was altered by external processes or concurrent operations between the user's confirmation and the tool's execution. The changes introduce a sophisticated conflict resolution strategy, ensuring data integrity and providing clear guidance to users when manual intervention is required.

Highlights

  • TOCTOU Vulnerability Resolution: Addressed a critical Time-of-Check to Time-of-Use (TOCTOU) race condition in the MemoryTool that could lead to data loss if the GEMINI.md file was modified externally between confirmation and execution.
  • Re-validation Layer: Introduced a new re-validation layer at the execution boundary to check for external modifications to the memory file before writing.
  • Dynamic Content Recomputation: Implemented logic to dynamically recompute the content against the live file state if the file has changed externally but the user did not manually edit the proposed content.
  • Conflict Abort Mechanism: Added a mechanism to gracefully abort the operation and prompt the user to resolve the collision if the file has changed externally AND the user manually modified the proposed content during the confirmation prompt.
  • Enhanced Test Coverage: Included two new test cases to memoryTool.test.ts to explicitly simulate and verify the TOCTOU concurrency race and the new dynamic re-evaluation logic.
Changelog
  • packages/core/src/tools/memoryTool.test.ts
    • Added a test case to verify dynamic content recomputation when the file changes externally between confirmation and execution.
    • Added a test case to ensure the tool aborts gracefully and formats an error when both the file changes externally and the user provides custom modifications.
  • packages/core/src/tools/memoryTool.ts
    • Introduced a private originalContent field to store the file's state at the time of initial read.
    • Stored the currentContent into this.originalContent during the shouldConfirmExecute phase.
    • Implemented logic within the execute method to detect external file modifications by comparing currentContent with originalContent.
    • Added conditional logic to either recompute content dynamically (if no user modification) or throw an error (if user modification and external change) to prevent data loss.
Activity
  • No specific activity beyond the initial pull request creation and description was provided in the context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a critical TOCTOU race condition in the MemoryTool, preventing potential data loss when the GEMINI.md file is modified externally during a tool operation. The solution is robust, involving re-reading the file before execution and handling conflicts gracefully. The addition of specific test cases to cover these race condition scenarios is excellent. I have one suggestion to refactor the new conditional logic in the execute method to improve its clarity and maintainability.

Comment on lines +227 to 257
const hasChanged =
this.originalContent !== undefined &&
currentContent !== this.originalContent;

if (hasChanged) {
if (modified_by_user && modified_content !== undefined) {
throw new Error(
'Conflict: GEMINI.md was modified externally while you were editing the prompt. Please review and retry to avoid data loss.',
);
} else {
contentToWrite = computeNewContent(currentContent, fact);
successMessage = `Okay, I've remembered that: "${sanitizedFact}"`;
}
} else {
// User approved the proposed change without modification.
// The source of truth is the exact content proposed during confirmation.
if (this.proposedNewContent === undefined) {
// This case can be hit in flows without a confirmation step (e.g., --auto-confirm).
// As a fallback, we recompute the content now. This is safe because
// computeNewContent sanitizes the input.
const currentContent = await readMemoryFileContent();
this.proposedNewContent = computeNewContent(currentContent, fact);
if (modified_by_user && modified_content !== undefined) {
// User modified the content, so that is the source of truth.
contentToWrite = modified_content;
successMessage = `Okay, I've updated the memory file with your modifications.`;
} else {
// User approved the proposed change without modification.
// The source of truth is the exact content proposed during confirmation.
if (this.proposedNewContent === undefined) {
// This case can be hit in flows without a confirmation step (e.g., --auto-confirm).
// As a fallback, we recompute the content now. This is safe because
// computeNewContent sanitizes the input.
this.proposedNewContent = computeNewContent(currentContent, fact);
}
contentToWrite = this.proposedNewContent;
successMessage = `Okay, I've remembered that: "${sanitizedFact}"`;
}
contentToWrite = this.proposedNewContent;
successMessage = `Okay, I've remembered that: "${sanitizedFact}"`;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The nested if/else structure to handle the race condition is logically correct but adds significant cognitive complexity by intertwining conflict detection with the different logic paths for writing content.

This can be refactored to be more linear and easier to reason about. By first checking for the unrecoverable conflict and exiting, and then handling the remaining cases, we can flatten the logic. This improves long-term maintainability and reduces the risk of introducing bugs in this critical section.

      const hasChanged =
        this.originalContent !== undefined &&
        currentContent !== this.originalContent;

      // Handle the unrecoverable conflict first.
      if (hasChanged && modified_by_user && modified_content !== undefined) {
        throw new Error(
          'Conflict: GEMINI.md was modified externally while you were editing the prompt. Please review and retry to avoid data loss.',
        );
      }

      // Determine the content to write and the success message for the remaining cases.
      if (modified_by_user && modified_content !== undefined) {
        // Case: User provided custom modifications, and no external conflict occurred.
        contentToWrite = modified_content;
        successMessage = `Okay, I've updated the memory file with your modifications.`;
      } else {
        // Case: No user modifications. Handle external changes or use cached content.
        if (hasChanged) {
          // The file changed externally, so we recompute the content from the new state.
          contentToWrite = computeNewContent(currentContent, fact);
        } else {
          // The file has not changed, so we can safely use the cached proposed content.
          if (this.proposedNewContent === undefined) {
            // Fallback for flows without a confirmation step (e.g., --auto-confirm).
            this.proposedNewContent = computeNewContent(currentContent, fact);
          }
          contentToWrite = this.proposedNewContent;
        }
        successMessage = `Okay, I've remembered that: "${sanitizedFact}"`;
      }

@gemini-cli gemini-cli bot added the area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality label Mar 1, 2026
@KumarADITHYA123
Copy link
Copy Markdown
Contributor Author

@gemini-code-assist review this pr

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request addresses a TOCTOU race condition in the MemoryTool. The implementation re-validates the file state before writing to prevent data loss from concurrent modifications. My review includes a suggestion to refine the error handling for the new conflict detection logic to provide a clearer error message to the user.

Comment on lines +303 to +305
expect(result.llmContent).toContain(
'Conflict: GEMINI.md was modified externally',
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

To accompany the suggested change of returning a ToolResult directly for the conflict error, this test's assertions should be updated to be more specific. Instead of a loose toContain check, it's better to assert the exact llmContent and returnDisplay values to ensure the error message is formatted correctly and prevent future regressions.

      const expectedError = 'Conflict: GEMINI.md was modified externally while you were editing the prompt. Please review and retry to avoid data loss.';
      expect(result.returnDisplay).toBe(expectedError);
      expect(result.llmContent).toBe(
        JSON.stringify({ success: false, error: expectedError })
      );

Comment on lines +232 to +236
if (hasChanged && modified_by_user && modified_content !== undefined) {
throw new Error(
'Conflict: GEMINI.md was modified externally while you were editing the prompt. Please review and retry to avoid data loss.',
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

While throwing an error here is functionally correct, it results in a redundant and verbose error message for the user due to the generic catch block (e.g., Error saving memory: Conflict...).

To provide a cleaner and more direct error message, this if block can be modified to return the ToolResult object directly instead of throwing. This avoids the generic error prefixing and presents the specific, user-friendly conflict message you've crafted.

      if (hasChanged && modified_by_user && modified_content !== undefined) {
        const errorMessage =
          'Conflict: GEMINI.md was modified externally while you were editing the prompt. Please review and retry to avoid data loss.';
        return {
          llmContent: JSON.stringify({
            success: false,
            error: errorMessage
          }),
          returnDisplay: errorMessage,
          error: {
            message: errorMessage,
            type: ToolErrorType.MEMORY_TOOL_EXECUTION_ERROR
          }
        };
      }

@KumarADITHYA123 KumarADITHYA123 force-pushed the fix-core-toctou-memorytool branch from b8215f8 to 69aec1c Compare March 12, 2026 14:47
@KumarADITHYA123
Copy link
Copy Markdown
Contributor Author

@gemini-code-assist review this pr

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the MemoryTool by implementing robust conflict detection and resolution for the GEMINI.md memory file. It introduces logic to detect external modifications to the memory file between the confirmation and execution phases of a memory operation. If the file changes externally and the user also provided custom modifications, the tool now aborts the operation and returns an error to prevent data loss. If the file changes externally but the user did not provide custom modifications, the tool dynamically recomputes the content based on the latest file state, effectively merging the new fact. A review comment suggests an improvement opportunity: when the tool silently merges a new fact due to external file modifications, the user should receive a more informative success message to improve the user experience.

Comment on lines 255 to 266
if (hasChanged) {
// The file changed externally, so we recompute the content from the new state.
contentToWrite = computeNewContent(currentContent, fact);
} else {
// The file has not changed, so we can safely use the cached proposed content.
if (this.proposedNewContent === undefined) {
// Fallback for flows without a confirmation step (e.g., --auto-confirm).
this.proposedNewContent = computeNewContent(currentContent, fact);
}
contentToWrite = this.proposedNewContent;
}
contentToWrite = this.proposedNewContent;
successMessage = `Okay, I've remembered that: "${sanitizedFact}"`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

When the memory file is modified externally (hasChanged is true), the tool silently merges the new fact. This could be confusing for the user as they are not notified of the merge. While this prevents data loss, the lack of feedback about the merge operation is a significant user experience issue. It would be better to provide a more informative success message in this scenario to make the user aware that their change was applied on top of other modifications.

        if (hasChanged) {
          // The file changed externally, so we recompute the content from the new state.
          contentToWrite = computeNewContent(currentContent, fact);
          successMessage = `Okay, I've remembered that: "${sanitizedFact}". Note: The memory file was modified externally and your change was merged.`;
        } else {
          // The file has not changed, so we can safely use the cached proposed content.
          if (this.proposedNewContent === undefined) {
            // Fallback for flows without a confirmation step (e.g., --auto-confirm).
            this.proposedNewContent = computeNewContent(currentContent, fact);
          }
          contentToWrite = this.proposedNewContent;
          successMessage = `Okay, I've remembered that: "${sanitizedFact}"`;
        }

@KumarADITHYA123 KumarADITHYA123 force-pushed the fix-core-toctou-memorytool branch from 69aec1c to cbcbbbc Compare March 12, 2026 15:05
@KumarADITHYA123
Copy link
Copy Markdown
Contributor Author

@gemini-code-assist LGTM?

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Hi @KumarADITHYA123, thanks for asking! My initial summary in comment #1 serves as my automated review of this pull request.

The "LGTM" (Looks Good To Me) is typically a signal from a human reviewer indicating their approval for merging. As an automated assistant, I don't provide a direct "LGTM" for merging, but rather highlight key changes and potential areas of concern.

If you'd like a more detailed, line-by-line code review with specific suggestions, please use the /gemini review command. This will generate a comprehensive review comment with my findings.

@KumarADITHYA123
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request addresses a critical Time-of-Check to Time-of-Use (TOCTOU) race condition in the MemoryTool to prevent data loss. The implemented solution correctly re-validates the memory file at the time of execution. The logic properly handles conflicts by either merging changes or aborting the operation when necessary. The new tests adequately cover the introduced scenarios. The changes are well-implemented and resolve the described vulnerability.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request addresses a Time-of-Check to Time-of-Use (TOCTOU) race condition in MemoryTool. The fix involves caching the file content at confirmation time and re-reading it at execution time to detect any modifications that occurred in the interim. The logic correctly handles two key scenarios: it automatically merges the user's intended change if the file was modified externally without a direct user edit, and it aborts the operation with a conflict error if both an external modification and a user edit occurred, thus preventing data loss. The new tests adequately cover these conflict resolution paths. I have reviewed the implementation and have not found any high or critical severity issues.

@KumarADITHYA123
Copy link
Copy Markdown
Contributor Author

@jacob314,@spencer426 @mrpmohiburrahman

The automated review is complete and flags zero remaining issues.

I've pushed a single squashed commit that incorporates the requested conflict resolution updates:

  1. Direct ToolResult returns for clean error stack preservation.
  2. Precise assertion matching in the test suite.
  3. Explicit terminal notifications for silent external merges.

Let me know if you need any adjustments to the re-validation logic before merging.

@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli bot commented Mar 15, 2026

Hi there! Thank you for your interest in contributing to Gemini CLI.

To ensure we maintain high code quality and focus on our prioritized roadmap, we have updated our contribution policy (see Discussion #17383).

We only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'. All other community pull requests are subject to closure after 14 days if they do not align with our current focus areas. For this reason, we strongly recommend that contributors only submit pull requests against issues explicitly labeled as 'help-wanted'.

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding and for being part of our community!

@gemini-cli gemini-cli bot closed this Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race Condition (TOCTOU) in MemoryTool Execution Leads to Silent Data Overwrite in GEMINI.md

1 participant