Skip to content

fix: dynamically update image URLs in agent_thoughts with valid signatures#31955

Open
jackjin1997 wants to merge 2 commits intolanggenius:mainfrom
jackjin1997:fix/re-sign-agent-thoughts
Open

fix: dynamically update image URLs in agent_thoughts with valid signatures#31955
jackjin1997 wants to merge 2 commits intolanggenius:mainfrom
jackjin1997:fix/re-sign-agent-thoughts

Conversation

@jackjin1997
Copy link

Fixes #31907.

Description

Currently, image URLs in agent_thoughts.thought and agent_thoughts.observation have static signatures that expire after ~10 minutes. This PR adds dynamic re-signing logic to these fields, consistent with how the answer field is handled in the Message model.

Changes

  • Added _re_sign_file_url helper and related properties (re_sign_file_url_thought, re_sign_file_url_observation) to MessageAgentThought model.
  • Updated AgentThought Pydantic schema to use these properties via validation_alias.

This is a more lightweight approach compared to other proposed fixes, focusing on the core issue of signature expiration in API responses.

Fixes #31907

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Feb 4, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jackjin1997, 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 image URLs embedded in agent thoughts and observations would expire due to static signatures. It introduces a robust solution to dynamically re-sign these URLs upon access, ensuring their continued validity. This change aligns the handling of these fields with existing practices for other signed URLs within the system, improving the reliability of displayed content.

Highlights

  • Dynamic URL Re-signing: Implemented a mechanism to dynamically re-sign image URLs found within agent_thoughts.thought and agent_thoughts.observation fields, preventing their expiration.
  • New Helper Function: Added a private helper method _re_sign_file_url to the MessageAgentThought model, responsible for identifying and re-signing various types of file URLs (tool files, upload files, image previews) within a given text.
  • Pydantic Schema Update: Modified the AgentThought Pydantic schema to utilize validation_alias for the thought and observation fields, directing them to new properties (re_sign_file_url_thought, re_sign_file_url_observation) that apply the URL re-signing logic.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • api/fields/conversation_fields.py
    • Modified the AgentThought Pydantic model to use validation_alias for the thought and observation fields, linking them to new re-signing properties.
  • api/models/model.py
    • Added a new private method _re_sign_file_url to MessageAgentThought for processing and re-signing file URLs within text.
    • Introduced re_sign_file_url_thought and re_sign_file_url_observation properties to MessageAgentThought to apply the URL re-signing logic to the respective fields.
Activity
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
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 aims to resolve expiring image URLs in agent thoughts by implementing dynamic re-signing logic. However, the _re_sign_file_url method in api/models/model.py introduces a critical security vulnerability: an 'Authorization Oracle'. This flaw allows re-signing of file URLs without verifying their original signature, enabling attackers to bypass access controls and obtain validly signed URLs for arbitrary files through prompt injection. It is crucial to verify the original signature before generating a new one. Furthermore, the _re_sign_file_url method also requires refactoring to correct filename parsing, reduce code duplication, and improve overall maintainability by extracting shared helper functions.

Comment on lines +1977 to +2048
def _re_sign_file_url(self, text: str | None) -> str | None:
if not text:
return text

pattern = r"\[!?.*?\]\((((http|https):\/\/.+)?\/files\/(tools\/)?[\w-]+.*?timestamp=.*&nonce=.*&sign=.*)\)"
matches = re.findall(pattern, text)

if not matches:
return text

urls = [match[0] for match in matches]

# remove duplicate urls
urls = list(set(urls))

if not urls:
return text

re_signed_text = text
for url in urls:
if "files/tools" in url:
# get tool file id
tool_file_id_pattern = r"\/files\/tools\/([\.\w-]+)?\?timestamp="
result = re.search(tool_file_id_pattern, url)
if not result:
continue

tool_file_id = result.group(1)

# get extension
if "." in tool_file_id:
split_result = tool_file_id.split(".")
extension = f".{split_result[-1]}"
if len(extension) > 10:
extension = ".bin"
tool_file_id = split_result[0]
else:
extension = ".bin"

if not tool_file_id:
continue

sign_url = sign_tool_file(tool_file_id=tool_file_id, extension=extension)
elif "file-preview" in url:
# get upload file id
upload_file_id_pattern = r"\/files\/([\w-]+)\/file-preview\?timestamp="
result = re.search(upload_file_id_pattern, url)
if not result:
continue

upload_file_id = result.group(1)
if not upload_file_id:
continue
sign_url = file_helpers.get_signed_file_url(upload_file_id)
elif "image-preview" in url:
# image-preview is deprecated, use file-preview instead
upload_file_id_pattern = r"\/files\/([\w-]+)\/image-preview\?timestamp="
result = re.search(upload_file_id_pattern, url)
if not result:
continue
upload_file_id = result.group(1)
if not upload_file_id:
continue
sign_url = file_helpers.get_signed_file_url(upload_file_id)
else:
continue
# if as_attachment is in the url, add it to the sign_url.
if "as_attachment" in url:
sign_url += "&as_attachment=true"
re_signed_text = re_signed_text.replace(url, sign_url)

return re_signed_text
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The _re_sign_file_url method in api/models/model.py presents a critical security vulnerability, functioning as an 'Authorization Oracle'. It re-signs file URLs without verifying the original signature, which means an attacker can inject malicious markdown links via prompt injection to obtain validly signed URLs for arbitrary files, thereby bypassing access controls. It is imperative to implement robust signature verification before re-signing any URL.

Beyond the security concern, this method also has several code quality issues:

  1. Code Duplication: The logic is very similar to re_sign_file_url_answer in the Message model and should be extracted into a shared helper function.
  2. Filename Parsing Bug: Using split('.') is incorrect for filenames with multiple dots; rsplit('.', 1) is the correct approach.
  3. Redundant Logic: The handling for file-preview and image-preview is duplicated and can be consolidated.

The provided code suggestion addresses the code quality issues, but the fundamental security flaw of missing signature verification must be prioritized and resolved.

    def _re_sign_file_url(self, text: str | None) -> str | None:
        if not text:
            return text

        pattern = r"[!?.*]((((http|https):\/\/.+)?\/files\/(tools\/)?[:w-]+.*?timestamp=.*&nonce=.*&sign=.*))"
        matches = re.findall(pattern, text)

        if not matches:
            return text

        urls = list(set(match[0] for match in matches))

        if not urls:
            return text

        re_signed_text = text
        for url in urls:
            if "files/tools" in url:
                # get tool file id
                tool_file_id_pattern = r"\/files\/tools\/([\.:w-]+)?\?timestamp="
                result = re.search(tool_file_id_pattern, url)
                if not result:
                    continue

                tool_file_id = result.group(1)

                # get extension
                if "." in tool_file_id:
                    split_result = tool_file_id.rsplit(".", 1)
                    tool_file_id = split_result[0]
                    extension = f".{split_result[-1]}"
                    if len(extension) > 10:
                        extension = ".bin"
                else:
                    extension = ".bin"

                if not tool_file_id:
                    continue

                sign_url = sign_tool_file(tool_file_id=tool_file_id, extension=extension)
            elif "file-preview" in url or "image-preview" in url:
                # get upload file id
                upload_file_id_pattern = r"\/files\/([:w-]+)\/(?:file-preview|image-preview)\?timestamp="
                result = re.search(upload_file_id_pattern, url)
                if not result:
                    continue

                upload_file_id = result.group(1)
                if not upload_file_id:
                    continue
                sign_url = file_helpers.get_signed_file_url(upload_file_id)
            else:
                continue
            # if as_attachment is in the url, add it to the sign_url.
            if "as_attachment" in url:
                sign_url += "&as_attachment=true"
            re_signed_text = re_signed_text.replace(url, sign_url)

        return re_signed_text

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Image URL in agent_thoughts (observation/thought) not dynamically updated with FILES_URL and signature

1 participant