Repository navigation
fix: [AI Agent] The same workflow functions correctly during backend debugging; however, when accessed via a web link, images fail to load during conversation, and certain nodes are missing from the execution details. - #7718
Conversation
…debugging; however, when accessed via a web link, images fail to load during conversation, and certain nodes are missing from the execution details.
| auth = request.META.get("HTTP_AUTHORIZATION") | ||
| cache.delete(get_key(token=auth[7:]), version=version) | ||
| return result.success(True) | ||
| return clear_file_auth_cookie(result.success(True), request.query_params.get("accessToken")) |
There was a problem hiding this comment.
I can only review the shown diff/surrounding lines, so I’m assuming the missing imports/settings are already correct.
Potential bugs / correctness issues
-
Cookie deletion depends on an exact
accessTokenmatch- Login sets the cookie at:
/chat/<access_token>/
- Logout and change-password clear it using:
request.query_params.get("accessToken")
- If the query parameter is missing, empty, or different from the token used at login,
delete_cookie()will use the wrong path and the HttpOnly cookie will not be deleted. - This is the biggest issue. Consider deriving the token from the authenticated request instead of a query parameter, e.g. from
Authorization: Bearer <token>, or store the cookie path server-side.
- Login sets the cookie at:
-
Access token in query string is risky
accessTokenin the query string can leak via access logs, browser history, Referer headers, proxies, etc.- Prefer the
Authorizationheader or server-side session/state. If backward compatibility requires the query param, validate it strictly and avoid logging it.
-
Cache deletion inconsistency in
ChangePassword- In change-password:
auth = request.META.get("HTTP_AUTHORIZATION") cache.delete(get_key(token=auth), version=version)
- In logout:
cache.delete(get_key(token=auth[7:]), version=version)
- If
get_key()expects the raw token, change-password is deleting with"Bearer <token>"instead of<token>, so the cache entry may not be removed. - Also,
auth[7:]will raiseTypeErrorifHTTP_AUTHORIZATIONis missing. Validate authentication before slicing.
- In change-password:
-
Cookie deletion must match the original cookie’s
domainandpath- Browser cookie deletion requires matching
name,domain, andpath. - If
generate()sets adomain,clear_file_auth_cookie()must pass the samedomain. secure,httponly, andsamesiteare not used for cookie matching, but keeping them consistent avoids confusion.
- Browser cookie deletion requires matching
-
Multiple path-scoped cookies may accumulate
- Because the path contains the access token, each login with a different token can create a separate
mk_file_authcookie:/chat/token-a/ /chat/token-b/ - Logout only clears the current path. Old cookies remain until expiry.
- If possible, use a fixed path such as
/chat/and rely on server-side validation, or explicitly clean up old paths.
- Because the path contains the access token, each login with a different token can create a separate
-
Raw token is embedded in the cookie path
- Cookie paths are not a strong security boundary, and the token may contain characters that are awkward in a
Set-Cookiepath. - Prefer a fixed cookie path, or at minimum validate/encode the token and keep it reasonably bounded.
- Cookie paths are not a strong security boundary, and the token may contain characters that are awkward in a
Compatibility / implementation notes
-
delete_cookie(..., samesite="Lax")depends on Django version- Modern Django supports
samesiteindelete_cookie(), but older versions may not. - If the project supports older Django, verify this. Otherwise it is harmless, though not required for deletion.
- Modern Django supports
-
Ensure
HttpResponseis imported- The new function uses
HttpResponsein the annotation. If it is not imported and annotations are evaluated at runtime, this will raiseNameError.
- The new function uses
-
clear_file_auth_cookie()assumesresult.success()returns an object withdelete_cookie()- DRF
Responsegenerally inherits from Django’s response classes and should supportdelete_cookie(), but ifresult.success()returns a custom wrapper, this will fail. - Consider typing it as the actual response type used by your project.
- DRF
Suggested cleanup / optimization
-
Centralize cookie set/clear logic
- Use one helper for setting and one for clearing so path, domain,
secure,httponly, andsamesitecannot drift:
def set_file_auth_cookie(response, token, path, secure): response.set_cookie( FILE_AUTH_COOKIE, value=token, max_age=7 * 24 * 3600, path=path, secure=secure, httponly=True, samesite="Lax", ) return response def clear_file_auth_cookie(response, path): response.delete_cookie(FILE_AUTH_COOKIE, path=path, samesite="Lax") return response
- Then compute the path once and pass it to both.
- Use one helper for setting and one for clearing so path, domain,
-
Prefer deriving the token from the authenticated request
- Example, if the cache token is the Bearer token:
auth = request.META.get("HTTP_AUTHORIZATION", "") if not auth.startswith("Bearer "): return result.error(...) access_token = auth[7:]
- Then use
access_tokenfor both cache invalidation and cookie-path clearing. This avoids relying on a query parameter and reduces leakage.
Summary
The main functional risk is that the cookie is only cleared if accessToken is supplied correctly as a query parameter. Missing or mismatched values leave the HttpOnly cookie in place. The main security concern is putting the access token in the query string. Also fix the "Bearer " handling inconsistency in change-password and add guards for missing Authorization headers.
| "source_type": FileSourceType.CHAT.value, | ||
| } | ||
| ).upload() | ||
| return file_url |
There was a problem hiding this comment.
Code Review
1. Potential issues (bugs)
- Changed
source_id/source_typesemantics silently: The file is now attributed to the chat rather than the application. If any existing logic queries files bysource_id = application_idwithFileSourceType.APPLICATION(e.g., cleanup, permissions, display), it will break or lose access to previously-uploaded files. Verify all consumers before merging. - Lost debug flag:
debugwas removed from meta. If downstream code readsmeta["debug"](e.g., to skip persistence or apply debug behavior), this is a regression. Confirm it's now handled elsewhere; if not, keep it. - Missing keys are not validated: If
chat_id,application_id, orchat_user_idis absent fromworkflow_params,Nonegets written into metadata/source_id silently. Add validation:if not chat_id or not application_id: raise ValueError("application_id and chat_id are required")
- Naming inconsistency: the param is
chat_user_idbut stored asuser_id. Fine, but document it, since key mismatch is a common bug source.
2. Minor style/robustness
- Consider
.get()defaults for consistency:workflow_params.get("chat_user_id", "")if empty strings are preferable toNonein storage. - The meta dict fits on one line longer than PEP8 allows; current formatting is fine, just be consistent with
_upload_tool_file(which presumably should get the sameuser_idtreatment — check for symmetry).
3. Optimization suggestions
- Minimal, but you could avoid rebuilding the dict inline:
for readability/greppability.
meta = { "chat_id": chat_id, "application_id": application_id, "user_id": chat_user_id, }
- Consider adding logging when required params are missing instead of only failing at serialization time.
Summary: No syntax problems, but the semantic change of source_id/source_type and removal of debug are behavior changes that must be verified against all consumers of this metadata.
| "source_type": FileSourceType.CHAT.value, | ||
| } | ||
| ).upload() | ||
| return file_url |
There was a problem hiding this comment.
Issues & Suggestions
-
Missing validation —
application_id,chat_id, andchat_user_idare all used without checking forNone. A missingchat_id(now used assource_id) could create Files with null/incorrect source linkage. Consider raising an error if they're absent:if not chat_id or not application_id: raise ValueError("chat_id and application_id are required")
-
Semantic change of
source_id—source_idchanged fromapplication_idtochat_id. If any existing logic (queries, permissions, cleanup) keys offsource_id = application_id, this is a breaking change for previously uploaded files. Migration/backfill may be needed. -
Key mismatch:
user_idvschat_user_id— meta storeschat_user_idunder"user_id". That's fine, but confirm downstream consumers expectuser_id, notchat_user_id. -
Removed
debughandling — anything relying onmeta["debug"]will now getNone/KeyError. Verify no caller depends on it. -
Hard requirement on
chat_user_id— if old callers don't pass it,user_idwill silently beNone. Add validation or a default. -
Minor/style — dict literal fits fine; consider f-string consistency elsewhere. No performance concerns here; changes are trivial cost-wise.
Overall: logic looks intentional (shifting file ownership from application-level to chat-level + user attribution), but items 1–4 deserve verification before merging.
fix: [AI Agent] The same workflow functions correctly during backend debugging; however, when accessed via a web link, images fail to load during conversation, and certain nodes are missing from the execution details.