fix(security): redact database URI passwords and mutable reasoning - #43940
fix(security): redact database URI passwords and mutable reasoning#43940sdyckjq-lab wants to merge 3 commits into
Conversation
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Security: Close persistence-boundary redaction gaps (#43666)
- Three distinct gaps closed:
- DB URIs:
postgresql+psycopg://...and other SQLAlchemy-style dialect+driver schemes now caught by_DB_CONNSTR_RE. Previously only barepostgres://,mysql://, etc. matched. - Reasoning fields:
reasoning,reasoning_content, andreasoning_detailsnow redacted at message construction before entering history/persistence. Previously only tool output and assistant content were redacted. - Compaction blocks: summarizer input and static fallback summary both verified to contain no plaintext secrets.
- DB URIs:
- Opaque fields preserved:
signature,encrypted_content,data, andencryptedtype items inreasoning_detailsare returned byte-exact to avoid breaking provider signature checks. This is a documented tradeoff (signed thinking blocks can contain secrets that won't be redacted). - Copy-on-write:
_redact_reasoning_detail()returns a new dict rather than mutating the input, preserving the raw API response for tool execution in the same turn. - Comprehensive tests (299 lines new test file + additions):
test_signed_detail_preserved_byte_exact,test_raw_api_detail_dict_not_mutated,test_redaction_disabled_is_noop,test_stored_bytes_equal_replayed_bytes, and the SQLAlchemy dialect+driver parametrized suite. - No regression risk: all changes are additive or defensive.
Reviewed by Hermes Agent
|
Verification Review — looks solid ✅ Reviewed the persistence-boundary redaction diff end-to-end. Key observations:
No findings. |
|
Thanks for the careful verification — I really appreciate you checking the copy-on-write behavior and the stored-bytes==replayed-bytes invariant. That was exactly the boundary I was trying to keep tight here. |
|
@alt-glitch thanks for triaging and labeling this. CI is green, the PR is mergeable, and there is external verification on the diff. Since this is scoped to #43666, I would appreciate a maintainer pass when you have bandwidth, especially on whether the fix matches the persistence-boundary constraints from the issue. Happy to adjust if you would prefer the scope split or narrowed further. |
egilewski
left a comment
There was a problem hiding this comment.
Recommendation: approve.
I reviewed this security PR against current GitHub main b6c7ebf028d8434270c9f446edb668c76485025e, PR base acd7932c0fc5f98331f01af6073115e6cf3ccf9d, and PR head 61908862a28ad3b38d2d881bc9ec13a800f85fcb.
Validation:
git rev-list --left-right --count refs/remotes/upstream/main...refs/remotes/upstream/pr/43940:244 1;git merge-tree --write-tree refs/remotes/upstream/main refs/remotes/upstream/pr/43940:5828999c11942eeeb59dc701e10a9455ec3bf01a;git diff --check refs/remotes/upstream/main...refs/remotes/upstream/pr/43940: passed. A no-commit merge of the PR head into current main also succeeded cleanly.- Synthetic persistence-boundary probe: current main reproduced the leak (
redact_sensitive_text,reasoning,reasoning_content, unsignedreasoning_details, and the syntheticstate.dbscan all still contained the test password). The same probe on the current-main replay of this PR removed the password from those redacted sinks and left the syntheticstate.dbscan clean, while preserving a signed reasoning detail byte-exact. python -B -m pytest -q tests/agent/test_redact.py tests/agent/test_persistence_redaction.py -p no:cacheprovider: 105 passed.python -B -m pytest -q tests/run_agent/test_run_agent.py -k 'reasoning or extra_content' -p no:cacheprovider: 40 passed, 337 deselected.python -B -m pytest -q tests/run_agent/test_provider_parity.py -k 'reasoning or extra_content or thought_signature or encrypted_content' -p no:cacheprovider: 24 passed, 69 deselected.python -B -m py_compile agent/redact.py agent/chat_completion_helpers.py tests/agent/test_redact.py tests/agent/test_persistence_redaction.py: passed.- CodeRabbit clean-pass review on the uncommitted current-main replay completed with no findings.
Finding: I did not find a blocker in the reviewed scope. The patch covers the reproduced DB URI/userinfo leak plus the plain assistant reasoning persistence paths, keeps the stored-bytes==replayed-bytes invariant, and preserves opaque provider-signed/encrypted reasoning material as required.
Signed: GPT-5.5-xhigh in Codex
|
@egilewski thank you for the careful review and for independently replaying this against current main. I really appreciate you checking both the reproduced leak and the signed/opaque reasoning preservation path — that validation is very helpful for this security-sensitive change. |
a42e0f6 to
859dd1e
Compare
|
@teknium1 since you opened #43666, I’d appreciate your view on whether #43940 matches the persistence-boundary constraints you described. I’ve kept it scoped to the three gaps from the issue; CI is green after rebasing today, and the PR is mergeable. There has also been independent community validation on the diff. Happy to narrow or adjust if you’d prefer a different shape for the fix. |
egilewski
left a comment
There was a problem hiding this comment.
Recommendation: approve
I reviewed this security-mode rebase against current GitHub main bee13817f06995cf690ee4e4aafed956be78ab69, PR base aca11c227eb7e8b2f53f6e130d6e922455a573c1, and PR head 859dd1ebc4385555cb49572045d5f606047db21d.
Validation:
- GitHub checks on PR head: all reported checks passed, with only expected skipped jobs.
git merge-tree --write-tree refs/remotes/upstream/main refs/remotes/upstream/pr/43940: passed with treef6524fdb50c91ebc761351d5649d6e834ceb73f2.git diff --check refs/remotes/upstream/main...refs/remotes/upstream/pr/43940: passed.- Current-main probe:
redact_sensitive_text("postgresql+psycopg://postgres:honchorulez@...")and assistantreasoningpersistence both retained the plaintext password, reproducing the #43666 persistence-boundary gap. - Same probe on PR head: both the DB URI and assistant
reasoningtext redacted the password to***. python -B -m pytest -q -p no:cacheprovider tests/agent/test_redact.py tests/agent/test_persistence_redaction.py: passed (105 passed).python -B -m pytest -q -p no:cacheprovider tests/run_agent/test_run_agent.py -k 'reasoning or extra_content': passed (40 passed, 338 deselected).python -B -m pytest -q -p no:cacheprovider tests/run_agent/test_provider_parity.py -k 'reasoning or extra_content or thought_signature or encrypted_content': passed (24 passed, 69 deselected).ruff check agent/redact.py agent/chat_completion_helpers.py tests/agent/test_redact.py tests/agent/test_persistence_redaction.py: passed.
Finding:
I did not find a blocker in the reviewed scope. The rebase still closes the DB URI and reasoning-field persistence gaps while preserving opaque provider replay material.
CodeRabbit ran successfully and reported one test-strength suggestion: changing the Gemini extra_content assertion from equality to identity. I checked the implementation and did not treat that as blocking because the replay contract is byte/value preservation, and the code can legitimately call model_dump() for provider objects while still preserving the serialized bytes. The raw-dict path currently preserves identity as well.
Signed: GPT-5.5-xhigh in Codex
859dd1e to
1b7e527
Compare
|
Rebased this onto current main and resolved the conflicts. The branch now follows #54136's direction: tool-call arguments remain preserved verbatim in replayed history, with builder + state DB round-trip tests covering that behavior. The remaining scope is narrowed to the #43666 persistence-boundary gaps this PR still owns: DB URI dialect+driver password coverage and safe-to-mutate reasoning-field redaction before assistant messages enter stored/replayed history or reasoning display callbacks. Signed/encrypted provider replay material remains unmodified, and #49556's broader compaction-hardening scope remains separate. Local validation included the focused redaction/persistence tests, reasoning + extra_content regressions, provider parity coverage, ruff, and a multi-pass /review sweep. |
1b7e527 to
d5d13a1
Compare
|
@teknium1 @alt-glitch quick update: rebased this PR onto current main, preserved #54136’s tool-call argument replay behavior, kept #49556’s broader compaction hardening separate, and fixed the CI flake that surfaced in the compression concurrency test. All required checks and Docker jobs are now green. Ready for another look when you have bandwidth. |
|
looks mergeable Security evidence:
I reproduced the original persistence-boundary leak on current main and validated the PR head with focused probes plus the redaction/persistence, run_agent reasoning, provider-parity, compression, and ruff checks. Signed: GPT-5.5-xhigh in Codex |
|
Thanks for the narrowly scoped persistence-boundary fix. I found no verified blocker in the reviewed scope. Current GitHub currently reports the PR mergeable and CLEAN. This is an automated hermes-sweeper review. |
f610536 to
8b011c6
Compare
|
Rebased #43940 onto current |
8b011c6 to
f941e41
Compare
|
Refreshed this PR from current
The PR description now reflects the exact boundary and residual risks. Ready for a fresh maintainer review. |
What does this PR do?
Refs #43666.
This PR closes the remaining persistence-boundary gaps that can be fixed without changing provider replay data:
This is intentionally a partial fix for #43666, so it uses
Refsrather thanFixes. It does not claim thatstate.dbcan never contain secrets.Related Issue
Type of Change
Changes Made
Database URI passwords
Mutable reasoning
reasoning,reasoning_content, or both, including partial-stream fallback and Bedrock used as an MoA aggregator.Replay boundary kept unchanged
The following remain unmodified because providers may require exact replay:
reasoning_detailsitem, including unsigned text blocks;reasoning_content;redacted_thinkingmaterial;Live streaming reasoning display is also unchanged. If secret redaction is disabled, this PR intentionally provides no storage protection. Existing databases are not rewritten.
How to Test
git diff --check.Local result on the refreshed head: 392 passed, 9 skipped. The database-redaction suite alone passes 113 tests. Ruff and
git diff --checkpass. Stress probes process 100,000 reasoning fragments in about 0.06 seconds and a one-million-character URI candidate in about 0.1 seconds on the test machine.Checklist
Code
Documentation & Housekeeping
cli-config.yaml.example: N/A, no config keys changedCONTRIBUTING.md/AGENTS.md: N/A, no contributor workflow changedResidual Risk
Co-authored-by: Kiro 有点Yes 246816394+sdyckjq-lab@users.noreply.github.com