Repository navigation
fix(api): enforce canonical hex format on validate_key_format (#2427) - #2491
RerankerGuo wants to merge 1 commit into
Conversation
…sor#2427) validate_key_format exists as a separate copy in two modules — src/memos/api/utils/api_keys.py (canonical home, used by the key-creation utilities) and src/memos/api/middleware/auth.py (the middleware that gates every authenticated request). Both used `int(hex_part, 16)` to verify the 64-char hex tail, which silently passes for: - a leading `+` or `-` sign (`int("+a..a", 16)` == ...), - a `0x` / `0X` prefix (`int("0xa..", 16)` == ...), - `_` separators (PEP 515: `int("a_a", 16)`), - surrounding whitespace. So the documented format `krlk_<64-lowercase-hex>` was not actually enforced — keys like `krlk_+a..a`, `krlk_0x..`, or `krlk_a_a` were accepted by the middleware, hashed verbatim, and looked up against the database, bypassing the canonical form that `generate_api_key()` produces. This commit: - Replaces the `int(hex_part, 16)` check in the canonical api_keys.py implementation with an anchored regex (`[0-9a-f]{64}`) so only the exact lowercase 64-hex form is valid. - Imports the fixed implementation into auth.py and keeps the public `validate_key_format` name as a thin re-export, so the middleware shares the same logic as the canonical utils copy. This prevents the two copies from drifting again — the original bug class. New tests/api/test_validate_key_format.py covers 21 cases: - canonical format and `generate_api_key()` output always pass - 15 non-canonical variants are rejected (signs, 0x prefix, underscore, uppercase, whitespace, wrong length, missing prefix, empty) - both copies (utils and middleware) enforce the same contract Fixes MemTensor#2427.
🤖 Open Code ReviewTarget: PR #2491 🔍 OpenCodeReview found 1 issue(s) in this PR. 1.
|
✅ Automated Test Results: PASSEDAll tests passed (21/21 executed). memos_python_core/changed-repo-python: 21/21. Duration: 9s Branch: |
Summary
validate_key_formatexists as a separate copy in two modules. Both usedint(hex_part, 16)to verify the 64-char hex tail, which silently passes for leading signs,0xprefixes,_separators, and surrounding whitespace — so the documented formatkrlk_<64-lowercase-hex>was not actually enforced at the middleware boundary.Root cause
Two copies of the same buggy validator:
src/memos/api/utils/api_keys.py:48— used by the key-creation utilitiessrc/memos/api/middleware/auth.py:65— gates every authenticated requestBoth check the tail via
int(hex_part, 16). CPython accepts:int(..., 16)result+aaaa…(64 chars)0xaaaa…(62 chars)aaaa_bbbb…(with_)AAAA…/AAAA…So
krlk_+a..a,krlk_0x..,krlk_a_a,krlk_A..A, etc. were all accepted by the middleware, hashed verbatim, and looked up against the database — bypassing the canonical form thatgenerate_api_key()produces (lowercase, no prefixes, no separators, no signs, no whitespace).Fix
Two changes that together eliminate the bug class:
src/memos/api/utils/api_keys.py: replace theint(hex_part, 16)check with an anchored regexre.fullmatch(r"[0-9a-f]{64}", hex_part). Only the exact 64-lowercase-hex form is valid.src/memos/api/middleware/auth.py: import the canonical implementation under the namevalidate_key_format_canonical, keep the public symbolvalidate_key_formatas a one-line re-export. This prevents the two copies from drifting again (the original bug class).Tests
New
tests/api/test_validate_key_format.py, 21 cases:generate_api_key()output (20 random samples) always pass0xprefix, underscore separators, uppercase hex, leading/trailing whitespace, wrong length, missingkrlk_prefix,Bearerprefix, empty stringutilsandmiddlewarecopies enforce the same contractVerification
Ruff:
ruff format --checkandruff checkboth clean on touched files (0 new findings vsmain; the 3 pre-existingDTZ/datetime.utcnowwarnings in api_keys.py are out of scope).Out of scope
Closes #2427