Commit Graph

10 Commits

Author SHA1 Message Date
jnjpng
28839f5180 fix: import cryptography default backend at top level (#8444)
* base

* comment
2026-01-12 10:57:49 -08:00
Sarah Wooders
6fddcc0c57 fix: fix agent loop (#8401) 2026-01-12 10:57:48 -08:00
jnjpng
d55fd69b7b chore: add comment and test for changing PBKDF2 iteration count (#8366)
base
2026-01-12 10:57:48 -08:00
jnjpng
b68e4e74f9 fix: replace cryptography with hashlib for encryption key derivation (#8364)
base
2026-01-12 10:57:48 -08:00
cthomas
a54513c343 feat: move decryption outside db session (#8323)
* feat: move decryption outside db session

* fix pydantic error
2026-01-12 10:57:48 -08:00
Kian Jones
bce1749408 fix: run PBKDF2 in thread pool to prevent event loop freeze (#6763)
* fix: run PBKDF2 in thread pool to prevent event loop freeze

Problem: Event loop freezes for 100-500ms during secret decryption, blocking
all HTTP requests and async operations. The diagnostic monitor detected the
main thread stuck in PBKDF2 HMAC SHA256 computation at:
  apps/core/letta/helpers/crypto_utils.py:51 (_derive_key)
  apps/core/letta/schemas/secret.py:161 (get_plaintext)

Root cause: PBKDF2 with 100k iterations is intentionally CPU-intensive for
security, but running it synchronously on the main thread blocks the event loop.

Stack trace showed:
  Thread 1 (Main): PBKDF2HMAC -> SHA256_Final -> sha256_block_data_order_avx2
  Event loop watchdog: Detected freeze at 01:11:44 (request started 01:12:03)

Solution:
1. Run PBKDF2 in ThreadPoolExecutor to avoid blocking event loop
2. Add async versions of encrypt/decrypt methods
3. Add LRU cache for derived keys (deterministic results)
4. Add async get_plaintext_async() method to Secret class

Changes:
- apps/core/letta/helpers/crypto_utils.py:
  - Added ThreadPoolExecutor for crypto operations
  - Added @lru_cache(maxsize=256) to _derive_key_cached()
  - Added _derive_key_async() using loop.run_in_executor()
  - Added encrypt_async() and decrypt_async() methods
  - Added warnings to sync methods about blocking behavior

- apps/core/letta/schemas/secret.py:
  - Added get_plaintext_async() method
  - Added warnings to get_plaintext() about blocking behavior

Benefits:
- Event loop no longer freezes during secret decryption
- HTTP requests continue processing while crypto runs in background
- Derived keys are cached, reducing CPU usage for repeated operations
- Backward compatible - sync methods still work for non-async code

Performance impact:
- Before: 100-500ms event loop block per decryption
- After: 100-500ms in thread pool (non-blocking) + LRU cache hits ~0.1ms

Next steps (follow-up PRs):
- Migrate all async callsites to use get_plaintext_async()
- Add metrics to track sync vs async usage
- Consider reducing PBKDF2 iterations if security allows

* update

* test

---------

Co-authored-by: Letta Bot <jinjpeng@gmail.com>
2025-12-15 12:03:09 -08:00
jnjpng
17a90538ca fix: exclude common API key prefixes from encryption detection (#6624)
* fix: exclude common API key prefixes from encryption detection

Add a list of known API key prefixes (OpenAI, Anthropic, GitHub, AWS,
Slack, etc.) to prevent is_encrypted() from incorrectly identifying
plaintext credentials as encrypted values.

* update

* test
2025-12-15 12:02:34 -08:00
jnjpng
7cac9a1a3e chore: update encryption key log line [LET-5474] (#5393)
update log

Co-authored-by: Letta Bot <noreply@letta.com>
2025-10-24 15:11:31 -07:00
jnjpng
631a27ee52 fix: fallback for no encryption key (#2968) 2025-09-16 17:25:39 -07:00
jnjpng
3711b5279c feat: encryption for mcp (#2937) 2025-09-16 11:56:34 -07:00