release: 3.37.0#59
Conversation
| custom_headers_env = os.environ.get("SUPERMEMORY_CUSTOM_HEADERS") | ||
| if custom_headers_env is not None: | ||
| parsed: dict[str, str] = {} | ||
| for line in custom_headers_env.split("\n"): | ||
| colon = line.find(":") | ||
| if colon >= 0: | ||
| parsed[line[:colon].strip()] = line[colon + 1 :].strip() | ||
| default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} |
There was a problem hiding this comment.
[High] Env-supplied headers can silently override the SDK's Authorization header
parsed is placed at the front of the merge ({**parsed, **(default_headers or {})}), but default_headers are applied before auth_headers in the base client. This means setting SUPERMEMORY_CUSTOM_HEADERS='Authorization: Bearer evil' will replace the bearer token derived from api_key, redirecting all requests to the wrong credentials without any warning.
Consider stripping or blocking reserved headers (Authorization, X-Api-Key, etc.) from parsed before merging, or placing parsed after auth_headers in the resolution order so auth always wins.
| for line in custom_headers_env.split("\n"): | ||
| colon = line.find(":") | ||
| if colon >= 0: | ||
| parsed[line[:colon].strip()] = line[colon + 1 :].strip() |
There was a problem hiding this comment.
[Medium] Malformed header lines produce empty header names instead of failing fast
A line like ': bad' passes the colon >= 0 check and inserts "" (empty string) as a header name. This doesn't raise at construction time — it propagates into the request and causes a generic runtime error later, making the root cause hard to diagnose.
Add a guard to skip (or raise) when the header name is empty after stripping:
name = line[:colon].strip()
if not name:
continue # or raise ValueError(f"Invalid header line: {line!r}")
parsed[name] = line[colon + 1:].strip()| custom_headers_env = os.environ.get("SUPERMEMORY_CUSTOM_HEADERS") | ||
| if custom_headers_env is not None: | ||
| parsed: dict[str, str] = {} | ||
| for line in custom_headers_env.split("\n"): | ||
| colon = line.find(":") | ||
| if colon >= 0: | ||
| parsed[line[:colon].strip()] = line[colon + 1 :].strip() | ||
| default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} |
There was a problem hiding this comment.
[High] Same Authorization override issue as in Supermemory (sync client)
Identical logic in AsyncSupermemory.__init__ — SUPERMEMORY_CUSTOM_HEADERS can silently replace the bearer token. Same fix applies: block reserved auth headers or ensure auth_headers always takes precedence.
| for line in custom_headers_env.split("\n"): | ||
| colon = line.find(":") | ||
| if colon >= 0: | ||
| parsed[line[:colon].strip()] = line[colon + 1 :].strip() |
There was a problem hiding this comment.
[Medium] Same empty header name issue in AsyncSupermemory
Identical to the sync client: malformed lines (e.g. ': bad') produce an empty header name that propagates silently into requests. Same guard needed here.
|
🤖 Release is at https://github.com/supermemoryai/python-sdk/releases/tag/v3.37.0 🌻 |
Automated Release PR
3.37.0 (2026-04-28)
Full Changelog: v3.36.0...v3.37.0
Features
Bug Fixes
This pull request is managed by Stainless's GitHub App.
The semver version number is based on included commit messages. Alternatively, you can manually set the version number in the title of this pull request.
For a better experience, it is recommended to use either rebase-merge or squash-merge when merging this pull request.
🔗 Stainless website
📚 Read the docs
🙋 Reach out for help or questions