Skip to content

Conversation

coder-jatin-s
Copy link

Fix #3138

I added a persistent prompt injection support and tests: introduced INJECTIONS_STATE_KEY and merged persisted prompt injections from callback/session state, added a sample before-model callback that persists injections and a unit test to verify cross-turn persistence

Copy link

google-cla bot commented Oct 12, 2025

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link

Summary of Changes

Hello @coder-jatin-s, 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 resolves an issue where prompt injections made through the before_model_callback were not being maintained across conversation turns. It introduces a robust system for persisting these injections by leveraging callback and session state, ensuring that the model consistently receives necessary contextual prompts. This enhancement significantly improves the ability to manage and maintain conversational context and model behavior over time.

Highlights

  • Persistent Prompt Injection: Introduced a mechanism to persist prompt injections across multiple turns in a conversation, ensuring that critical prompts remain active throughout the interaction.
  • State Management for Injections: Implemented logic to store and retrieve prompt injections from either the callback_context.state or invocation_context.session.state using a dedicated key (__persisted_prompt_injections).
  • Sample Callback for Persistence: Added a new sample before_model_callback_persist_injections function to demonstrate how user code can effectively store and manage persistent prompt injections.
  • Duplicate Injection Prevention: Enhanced the injection process to check for existing prompts in the llm_request.contents before adding new ones, preventing redundant or duplicate injections.
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

@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 introduces support for persistent prompt injections using a before_model_callback. The changes look good conceptually, but there are a few areas for improvement in the implementation. My review focuses on enhancing code safety, efficiency, and maintainability, particularly in base_llm_flow.py where I've suggested a refactoring to address a risky try-except block, use of magic strings, and inefficient looping. I've also provided feedback on the sample callback to promote better practices.

Comment on lines +802 to +853
state = getattr(callback_context, 'state', None)
if state is None:
injections = None
else:
injections = state.get("__persisted_prompt_injections")
if not injections:
try:
session_state = getattr(invocation_context, 'session', None)
if session_state and getattr(session_state, 'state', None) is not None:
persisted = session_state.state.get("__persisted_prompt_injections")
if persisted:
injections = persisted
except Exception:
injections = injections

if injections:
if not isinstance(injections, list):
injections = [injections]
for inj in injections:
if not inj:
continue
already = False
inj_text = None
if isinstance(inj, str):
inj_text = inj
else:
parts = getattr(inj, 'parts', None)
if parts and len(parts) and hasattr(parts[0], 'text'):
inj_text = parts[0].text
else:
inj_text = None

if inj_text:
for c in list(llm_request.contents or []):
if not c.parts:
continue
for part in c.parts:
if part and getattr(part, 'text', None) == inj_text:
already = True
break
if already:
break
if already:
continue

llm_request.contents = llm_request.contents or []
if isinstance(inj, str):
llm_request.contents.insert(
0, types.Content(role="user", parts=[types.Part(text=inj)])
)
else:
llm_request.contents.insert(0, inj)

Choose a reason for hiding this comment

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

high

This new block for handling prompt injections has several areas for improvement regarding safety, efficiency, and maintainability:

  1. Magic String: The key __persisted_prompt_injections is hardcoded. This should be defined as a module-level constant (e.g., INJECTIONS_STATE_KEY) and used consistently to avoid typos and improve maintainability.
  2. Unsafe Exception Handling: The try...except Exception: block silences all errors, which can hide critical bugs. It's better to use safer attribute access.
  3. Inefficient Duplicate Check: The nested loops for checking for duplicate injections are inefficient. Using a set for lookups would be more performant.
  4. Overly Defensive Code: The checks for callback_context.state being None are unnecessary as it's guaranteed to be a State object.

Here is a refactored version of the block that addresses these points:

    injections = callback_context.state.get("__persisted_prompt_injections")
    if not injections:
      session = getattr(invocation_context, 'session', None)
      if session:
        injections = session.state.get("__persisted_prompt_injections")

    if injections:
      if not isinstance(injections, list):
        injections = [injections]

      llm_request.contents = llm_request.contents or []
      existing_texts = {
          part.text
          for c in llm_request.contents
          for part in (c.parts or [])
          if getattr(part, 'text', None)
      }

      for inj in injections:
        if not inj:
          continue

        inj_text = None
        if isinstance(inj, str):
          inj_text = inj
        elif getattr(inj, 'parts', None) and inj.parts and hasattr(inj.parts[0], 'text'):
          inj_text = inj.parts[0].text

        if inj_text and inj_text in existing_texts:
          continue

        if isinstance(inj, str):
          llm_request.contents.insert(
              0, types.Content(role="user", parts=[types.Part(text=inj)])
          )
        else:
          llm_request.contents.insert(0, inj)

This function demonstrates how user code can store small text injections
in the callback context state so future requests will include them.
"""
global _index

Choose a reason for hiding this comment

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

medium

Using a global variable _index is not thread-safe and is not a recommended pattern, especially for a sample file that users might copy. In a real-world scenario with concurrent requests, this could lead to race conditions. A better approach is to store the index in the callback_context.state, which is scoped to the user's session. For example:

_index = callback_context.state.setdefault('my_injection_index', -1) + 1
callback_context.state['my_injection_index'] = _index

Comment on lines +32 to +42
for inj in injections:
found = False
for c in llm_request.contents:
for part in getattr(c, 'parts', []):
if getattr(part, 'text', None) == inj:
found = True
break
if found:
break
if not found:
llm_request.contents.insert(0, types.Content(role='user', parts=[types.Part(text=inj)]))

Choose a reason for hiding this comment

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

medium

The nested loops to check if an injection already exists are inefficient. This can be optimized by first building a set of all existing text from llm_request.contents for fast O(1) lookups.

Suggested change
for inj in injections:
found = False
for c in llm_request.contents:
for part in getattr(c, 'parts', []):
if getattr(part, 'text', None) == inj:
found = True
break
if found:
break
if not found:
llm_request.contents.insert(0, types.Content(role='user', parts=[types.Part(text=inj)]))
existing_texts = {
part.text
for c in llm_request.contents
for part in getattr(c, 'parts', [])
if getattr(part, 'text')
}
for inj in injections:
if inj not in existing_texts:
llm_request.contents.insert(0, types.Content(role='user', parts=[types.Part(text=inj)]))

@ryanaiagent ryanaiagent self-assigned this Oct 14, 2025
@ryanaiagent
Copy link
Collaborator

HI @coder-jatin-s, Thanks for your contribution! This looks promising. Here are the next steps

Sign the CLA: This is the first requirement before we can accept the code.

Add Unit Tests: After the CLA is signed, please add tests to cover the new functionality.

Let us know if you have any questions!

@ryanaiagent ryanaiagent added core [Component] This issue is related to the core interface and implementation request clarification [Status] The maintainer need clarification or more information from the author labels Oct 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core [Component] This issue is related to the core interface and implementation request clarification [Status] The maintainer need clarification or more information from the author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing injected prompt in conversation context, prompt injected using before_model_callback method

2 participants