🔴 Required Information
Describe the Bug:
InMemoryArtifactService and GcsArtifactService lay session-scoped and
user-scoped artifacts out in the same flat namespace, using the literal
segment "user" to mark user-scoped artifacts (_artifact_path/
_get_blob_prefix return f"{app_name}/{user_id}/user/{filename}" for
user-namespaced files, and f"{app_name}/{user_id}/{session_id}/{filename}"
otherwise). The reserved literal "user" is never validated against, so a
session actually named "user" writes its artifacts into the same prefix
that is supposed to be reserved for user-scoped ones.
The consequence isn't just a naming collision: list_artifact_keys() for
an unrelated session incorrectly returns filenames that belong to the
"user"-named session, and attempting to load one of those filenames
returns None — the listing and the load disagree, so a caller that
iterates keys and fetches each one gets an unpredictable miss.
Steps to Reproduce:
pip install google-adk
- Run:
import asyncio
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.genai import types
async def main():
svc = InMemoryArtifactService()
# A session literally named "user" -- collides with the reserved segment.
await svc.save_artifact(
app_name="a", user_id="u", session_id="user",
filename="from_session_user.txt", artifact=types.Part(text="x"),
)
# A genuinely user-scoped artifact, and an unrelated session "s2".
await svc.save_artifact(
app_name="a", user_id="u", session_id="s2",
filename="user:shared.txt", artifact=types.Part(text="y"),
)
print(await svc.list_artifact_keys(app_name="a", user_id="u", session_id="s2"))
print(await svc.load_artifact(
app_name="a", user_id="u", session_id="s2",
filename="from_session_user.txt",
))
asyncio.run(main())
Expected Behavior:
session_id="user" should be rejected outright (it's an internally
reserved value), so this situation can't arise. Failing that, at minimum
list_artifact_keys(session_id="s2") should not include filenames that
belong to a different session.
Observed Behavior:
['from_session_user.txt', 'user:shared.txt']
None
list_artifact_keys(session_id="s2") returns from_session_user.txt, a
filename that belongs to session "user", not "s2". Loading it via
session_id="s2" then returns None, since it's actually stored under the
reserved .../user/... prefix.
Environment Details:
- ADK Library Version:
main
- Desktop OS: N/A — reproduces regardless of OS
- Python Version: 3.11+
Model Information:
- Are you using LiteLLM: N/A
- Which model is being used: N/A — no model involved, this is the artifact
storage layer alone
🟡 Optional Information
Additional Context:
Surfaced during review of #7030 (a related but distinct bug: session_id
whitespace normalization, fixed by #6958). This is a different root cause —
a reserved literal value, not padding — and does not depend on that fix.
Confirmed FileArtifactService is not affected: it lays session-scoped
artifacts out under their own sessions/<id>/ subtree, distinct from the
user-scoped artifacts/ subtree, so a session named "user" cannot
collide with it there.
Fix proposed in the linked PR: reject session_id == "user" outright in
InMemoryArtifactService and GcsArtifactService.
How often has this issue occurred?:
- Always (100%) — deterministic, no timing involved
🔴 Required Information
Describe the Bug:
InMemoryArtifactServiceandGcsArtifactServicelay session-scoped anduser-scoped artifacts out in the same flat namespace, using the literal
segment
"user"to mark user-scoped artifacts (_artifact_path/_get_blob_prefixreturnf"{app_name}/{user_id}/user/{filename}"foruser-namespaced files, and
f"{app_name}/{user_id}/{session_id}/{filename}"otherwise). The reserved literal
"user"is never validated against, so asession actually named
"user"writes its artifacts into the same prefixthat is supposed to be reserved for user-scoped ones.
The consequence isn't just a naming collision:
list_artifact_keys()foran unrelated session incorrectly returns filenames that belong to the
"user"-named session, and attempting to load one of those filenamesreturns
None— the listing and the load disagree, so a caller thatiterates keys and fetches each one gets an unpredictable miss.
Steps to Reproduce:
pip install google-adkExpected Behavior:
session_id="user"should be rejected outright (it's an internallyreserved value), so this situation can't arise. Failing that, at minimum
list_artifact_keys(session_id="s2")should not include filenames thatbelong to a different session.
Observed Behavior:
list_artifact_keys(session_id="s2")returnsfrom_session_user.txt, afilename that belongs to session
"user", not"s2". Loading it viasession_id="s2"then returnsNone, since it's actually stored under thereserved
.../user/...prefix.Environment Details:
mainModel Information:
storage layer alone
🟡 Optional Information
Additional Context:
Surfaced during review of #7030 (a related but distinct bug:
session_idwhitespace normalization, fixed by #6958). This is a different root cause —
a reserved literal value, not padding — and does not depend on that fix.
Confirmed
FileArtifactServiceis not affected: it lays session-scopedartifacts out under their own
sessions/<id>/subtree, distinct from theuser-scoped
artifacts/subtree, so a session named"user"cannotcollide with it there.
Fix proposed in the linked PR: reject
session_id == "user"outright inInMemoryArtifactServiceandGcsArtifactService.How often has this issue occurred?: