Fix multi-tenant security: RLS-safe cleanup, scoped clear, stronger argon2#93
Merged
Conversation
…rgon2 - _do_cleanup() uses SET LOCAL row_security = off for cross-owner expired entry cleanup - clear() now requires owner_id, scoped DELETEs with RLS context - Argon2 time_cost bumped from 2 to 3 for stronger password hashing - New tests: cleanup cross-owner deletion, clear owner isolation - Externalized SQL for clear operations and disable_row_security Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
cmeans
commented
Mar 29, 2026
Owner
Author
cmeans
left a comment
There was a problem hiding this comment.
QA Review — PR #93: Fix multi-tenant security
Summary
Three targeted security fixes. All well-scoped, no over-engineering.
Code Review
1. _do_cleanup() RLS-safe
- Now uses
conn.transaction()+SET LOCAL row_security = offbefore cleanup SET LOCALscopes to transaction — pool connections stay clean for next user- SQL extracted to
disable_row_security.sql - Correct: cleanup is a system-wide task, not tenant-scoped
2. clear(owner_id) scoped
- Was
clear()with no owner filter — cross-tenant data loss risk - Now takes
owner_id, all 4 DELETE statements useWHERE owner_id = %s+ RLS context - 4 new SQL files:
clear_reads.sql,clear_actions.sql,clear_embeddings.sql,clear_entries.sql - Store protocol updated, all callers updated (conftest, benchmark, tests)
- New
test_clear_isolates_ownersverifies cross-tenant isolation - New
test_cleanup_respects_rlsverifies cross-owner cleanup
3. Argon2 time_cost=3
PasswordHasher(time_cost=3)— one line incli.py- argon2 self-describes params in hash string, so existing
t=2hashes still verify - Verified: hash output contains
t=3
CI Gate Check
| Check | Conclusion |
|---|---|
| lint | ✅ SUCCESS |
| typecheck | ✅ SUCCESS |
| test (3.10) | ✅ SUCCESS |
| test (3.11) | ✅ SUCCESS |
| test (3.12) | ✅ SUCCESS |
| codecov/patch | ✅ SUCCESS |
Zero non-SUCCESS/non-SKIPPED checks.
Test Results
| Check | Result |
|---|---|
| pytest (457 tests) | ✅ 457/457 pass |
| ruff (src/, alembic/) | ✅ Clean |
| mypy | ✅ Clean |
| CI | ✅ All green |
| Manual #1: Cross-owner cleanup | ✅ Expired entries for owner-a and owner-b both cleaned after debounce |
| Manual #2: Argon2 time_cost=3 | ✅ Hash contains t=3 parameter |
Verdict
Zero findings. All CI green. All tests pass. Ready for QA Signoff.
Owner
Author
|
Adding Ready for QA Signoff — all 3 security fixes verified (RLS-safe cleanup, owner-scoped clear, argon2 time_cost=3). CI fully green (codecov SUCCESS), 457/457 pytest, manual tests pass. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cleanup_expiredRLS-safe: background cleanup now usesSET LOCAL row_security = offin a transaction so expired entries are cleaned across all owners regardless of RLS enforcementclear()scoped to owner: requiresowner_idparameter, deletes only that owner's data viaWHERE owner_id = %s+ RLS context — prevents cross-tenant data losstime_costbumped to 3: stronger password hashing for new/changed passwords (existing hashes remain valid — argon2 stores params in the hash string)QA
Prerequisites
pip install -e ".[dev]"AWARENESS_PORT=8421)Manual tests (via MCP tools)
Create entries with short TTL for two different owners, wait for expiry + cleanup trigger, verify both are gone:
Expected: both expired entries cleaned regardless of which owner triggered cleanup
Expected: exported hash contains
t=3parameter (e.g.,$argon2id$v=19$m=65536,t=3,p=4$...)Expected: token generates successfully (password verification uses hash-embedded params, not current PasswordHasher config)
🤖 Generated with Claude Code