Three defense-in-depth gaps from GHSA-3wgj-c2hg-vm6q that the v0.9.5
serving-side fix didn't address:
1. _process_picture_url (utils/oauth.py): MIME was inferred from the
URL extension via mimetypes.guess_type, the upstream Content-Type
was discarded, and there was no allowlist. An SVG picture URL
produced data:image/svg+xml;base64,... in the user's
profile_image_url. Switch to the upstream Content-Type and gate it
against PROFILE_IMAGE_ALLOWED_MIME_TYPES (the same env var the
serving endpoint already uses); fall back to /user.png if the MIME
isn't in the allowlist. Also pass allow_redirects=AIOHTTP_CLIENT_
ALLOW_REDIRECTS on the aiohttp session.get — the existing
validate_url() only checks the initial URL, redirects to internal
targets would otherwise still be followed (same class as the
rh5x-h6pp-cjj6 cluster, sixth call site).
2. update_user_profile_image_url_by_id (models/users.py): SQLAlchemy
write path bypassed the Pydantic form validators, so anything
stored via OAuth or any other non-form caller landed in the column
unchallenged. Run validate_profile_image_url at the storage layer
before the assignment.
3. insert_new_user (models/users.py): same gap on the new-user path
used by Auths.insert_new_auth (OAuth signup, LDAP). Same
storage-layer call to validate_profile_image_url, falling back to
/user.png if the supplied value doesn't pass.
The serving-endpoint allowlist landed in v0.9.5 already broke the
exploit chain matte1782 demonstrated (browser never receives
Content-Type: image/svg+xml), but bad data was still being written
to the DB and the upstream MIME was never trusted. These three
fixes harden the ingestion + storage layers so future serving paths
or DB readers don't have to assume the column is clean.
Reported by matte1782 in GHSA-3wgj-c2hg-vm6q.
Co-authored-by: matte1782 <matte1782@users.noreply.github.com>
* fix: prevent redirect-based SSRF in get_image_base64_from_url
Cohort follow-up to PR #24491. That PR patched three call sites
(SafeWebBaseLoader._scrape, get_content_from_url, load_url_image) to
pass allow_redirects=False on the underlying HTTP client; this fourth
call site in utils/files.py was missed.
get_image_base64_from_url() is invoked from convert_url_images_to_base64
in utils/middleware.py on every /api/chat/completions request whose
message content includes an image_url part. validate_url() is called on
the originally-submitted URL only; the aiohttp session.get() call had
no allow_redirects argument and the shared session pool does not
override the aiohttp default (allow_redirects=True). An authenticated
user sending a chat message with image_url pointing at an attacker host
that 302-redirects to 169.254.169.254 / 127.0.0.1 / RFC1918 reached the
internal target. This is the most reachable variant in the redirect
cluster: no special endpoint, no admin permission, no feature flag.
Apply the same one-line fix as the other three call sites: pass
allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS (defaults to False).
Reported by nayakchinmohan in GHSA-88jq-grjp-jx6f; consolidated under
GHSA-rh5x-h6pp-cjj6.
Co-authored-by: nayakchinmohan <nayakchinmohan@users.noreply.github.com>
* fix: enforce collection write access on process_file endpoint
Cohort follow-up to ba83613ff. That commit added _validate_collection_access
to process_text and process_web (the user-supplied collection_name path)
but missed process_file in the same router.
process_file accepts a user-supplied collection_name and writes the file's
embedded content into that collection via save_docs_to_vector_db. The
file_id is gated by file ownership (line 1562) but collection_name was
unchecked, so an authenticated user could append content from a file they
own into another user's knowledge-base collection by passing the victim's
KB UUID as collection_name. Identical pattern to the process_text and
process_web gaps that ba83613ff closed.
Apply the same one-line gate as the sibling endpoints: when
collection_name is user-supplied (not the default file-{file.id} fallback),
require write access via _validate_collection_access. The shared validator
delegates to filter_accessible_collections, which already correctly
handles file-* prefixes (via has_access_to_file) and KB UUIDs
(via Knowledges.check_access_by_user_id) — admins bypass.
Reported by tenbbughunters (Tenable) in GHSA-4g37-7p2c-38r9 (the
comprehensive write-path filing covering process_text / process_file /
process_web / process_youtube and the _validate_collection_access UUID
root cause), and independently re-identified for the missed process_file
call site by kodareef5 in GHSA-4m74-3cmc-293g.
Co-authored-by: tenbbughunters <tenbbughunters@users.noreply.github.com>
Co-authored-by: kodareef5 <kodareef5@users.noreply.github.com>
* fix: enforce collection write access on process_files_batch endpoint
Cohort follow-up to ba83613ff and the prior process_file fix on this
branch. process_files_batch (line 2604) is the third write endpoint in
the same router that accepts a user-supplied collection_name; it was
covered in the same Tenable filing as process_file and was missed by
the same cohort fix. The endpoint validates per-file ownership at line
2642 but does not check whether the caller has write access to the
target collection_name before save_docs_to_vector_db writes into it
at line 2683-2690 with add=True.
Apply the same one-line gate as the sibling endpoints. Validate only
when collection_name is user-supplied (truthy) so the existing fall
through behavior for the None case is unchanged.
Same Tenable / kodareef5 cohort as the previous commit.
Co-authored-by: tenbbughunters <tenbbughunters@users.noreply.github.com>
Co-authored-by: kodareef5 <kodareef5@users.noreply.github.com>
---------
Co-authored-by: nayakchinmohan <nayakchinmohan@users.noreply.github.com>
Co-authored-by: tenbbughunters <tenbbughunters@users.noreply.github.com>
Co-authored-by: kodareef5 <kodareef5@users.noreply.github.com>
After migration to async db operations, the throttle decorator also
needs to support async. Since the decorator is only used for async funcs
now, we can just switch it to async instead of supporting sync and async
at the same time.
Signed-off-by: Adam Tao <tcx4c70@gmail.com>