Skip to content

Artifact services (in_memory, file, GCS) key storage on raw session_id — padded id creates a session it cannot reach #7030

Description

@MUHAMMEDHAFEEZ

Required Information

Describe the Bug:

InMemorySessionService and SqliteSessionService normalize session_id
(strip surrounding whitespace) before using it as a storage key (#6892,
#6941/#6942). The artifact services never got the same treatment:
InMemoryArtifactService, FileArtifactService, and GcsArtifactService
all key session-scoped artifact storage on the raw, un-normalized
session_id string.

The result: a session created with a padded id (e.g. "order-42\n") is
stored, read, and deleted under the trimmed id "order-42" — but an
artifact saved against that same padded id lands in a sibling storage
namespace the session's own (trimmed) id can never reach. One logical
session, two artifact namespaces.

A related, narrower issue: artifact_util.validate_path_segment only
rejects an empty string, so a whitespace-only session_id (e.g. " ")
previously slipped through as a literal path/key segment instead of being
rejected or treated as "no id".

A third issue: artifact references (the artifact://... URI a caller can
pass as file_data.file_uri to alias another artifact) embed session_id
directly in the URI, and the same-session scope check
(validate_artifact_reference_scope) compares the caller's raw
session_id against the id parsed back out of that URI. Without
normalizing both sides the same way, a caller consistently using a padded
id can get a spurious InputValidationError: Session-scoped artifact references must stay within the same session scope.

Steps to Reproduce:

  1. pip install google-adk
  2. Run:
import asyncio
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.genai import types

async def main():
    session_service = InMemorySessionService()
    artifact_service = InMemoryArtifactService()

    await session_service.create_session(
        app_name="a", user_id="u", session_id="s1\n"
    )  # stored under the trimmed id "s1"

    await artifact_service.save_artifact(
        app_name="a", user_id="u", session_id="s1\n",
        filename="f.txt", artifact=types.Part(text="hi"),
    )

    keys = await artifact_service.list_artifact_keys(
        app_name="a", user_id="u", session_id="s1"
    )
    print("artifact keys visible to the session's own id:", keys)

asyncio.run(main())

Expected Behavior:

list_artifact_keys(session_id="s1") should return ["f.txt"], since that
is the id the session itself is stored and reachable under.

Observed Behavior:

It returns []. The artifact saved with the padded id "s1\n" is invisible
under the session's own (trimmed) id "s1".

Environment Details:

Model Information:

  • Are you using LiteLLM: N/A
  • Which model is being used: N/A — no model involved, this is the
    session/artifact storage layer alone

🟡 Optional Information

Regression:

Not a regression from a previously-working state; the artifact services
never normalized session_id. It became reachable/exploitable in the same
way #6941 was: #6892 made get_session/create_session succeed under a
normalized id, which makes it easy to reach this artifact-layer split by
simply saving an artifact with the same (un-normalized) id used to create
the session.

Additional Context:

This same gap was independently flagged during review of #6942 (see that
PR's review thread) for InMemoryArtifactService/FileArtifactService,
and confirmed by the PR author as real but out of scope for that change.
While verifying it, GcsArtifactService was found to have the identical
gap as well (not previously mentioned).

Fix proposed in #6958: adds artifact_util.normalize_session_id() and
applies it at every entry point across all three artifact service
backends that builds a storage key/path or artifact-reference URI from a
caller-supplied session_id.

How often has this issue occurred?:

  • Always (100%) — deterministic, no timing involved

Activity

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

Metadata

Metadata

Assignees

Labels

services[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions