Skip to content

fix(EncryptedSession): add strict mode to reject non-encrypted items - #4376

Closed
SashaMIT wants to merge 1 commit into
openai:mainfrom
SashaMIT:fix/encrypt-session-strict-mode
Closed

fix(EncryptedSession): add strict mode to reject non-encrypted items#4376
SashaMIT wants to merge 1 commit into
openai:mainfrom
SashaMIT:fix/encrypt-session-strict-mode

Conversation

@SashaMIT

Copy link
Copy Markdown

Summary

EncryptedSession wraps an underlying session store with Fernet AEAD so stored conversation items are encrypted and integrity-protected at rest. The read path, however, currently returns any stored item that lacks the __enc__ envelope completely verbatim: _unwrap passes non-envelope items straight through, so get_items and pop_item feed unauthenticated plaintext into model context with no signal to the operator.

That passthrough is useful when migrating a plaintext store onto encrypted storage, so this PR keeps it as the default and adds an opt-in strict mode rather than changing behavior:

  • New constructor flag allow_plaintext_passthrough (default True, behavior identical to today).
  • When set to False, non-envelope items are dropped with a warning log on both read paths (get_items via _unwrap_valid_items, and the pop_item retry loop), so only authenticated ciphertext reaches the model.

This is a hardening / fail-closed option, not a vulnerability fix: planting items already requires write access to the underlying store, but operators who treat the store as untrusted at rest now have a way to enforce the integrity the wrapper otherwise implies.

A minimum-length check for raw-string encryption_key values was considered but left out to avoid breaking existing deployments. Happy to add a short note to docs/sessions/encrypted_session.md if you would like that in this PR.

Test plan

  • pytest tests/extensions/memory/test_encrypt_session.py: 24 passed (4 new)
  • New coverage: default passthrough unchanged, strict mode drops non-envelope items with a warning, strict mode still reads valid envelopes, pop_item skips non-envelope items
  • ruff check, ruff format --check, and mypy clean on touched files

Made with Cursor

EncryptedSession wraps an untrusted-at-rest store with Fernet AEAD, but
the read path returned any item lacking the __enc__ envelope verbatim,
so unauthenticated plaintext could enter model context with no signal.

Add allow_plaintext_passthrough (default True, preserving current
behavior for plaintext-to-encrypted migration). When set to False,
non-envelope items are dropped with a warning log on both the get_items
and pop_item read paths.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a99cfd9ec0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +174 to +179
if not self._allow_plaintext_passthrough:
logger.warning(
"EncryptedSession dropping non-encrypted item from session store "
"(allow_plaintext_passthrough=False)."
)
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve plaintext tails during strict pop

When strict mode encounters a plaintext tail, returning None here causes the existing pop_item() loop to discard that record and continue until it also removes and returns an older encrypted record. A single pop can therefore delete multiple persisted items; more critically, if a plaintext record is appended concurrently after Runner verifies an attempt-owned suffix, retry rewind silently deletes that unowned record and still reports success after receiving the expected encrypted item. Strict rejection must not destructively scan past an unowned tail.

AGENTS.md reference: AGENTS.md:L155-L156

Useful? React with 👍 / 👎.

Comment on lines +175 to +178
logger.warning(
"EncryptedSession dropping non-encrypted item from session store "
"(allow_plaintext_passthrough=False)."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Aggregate strict-mode rejection warnings

When strict mode is used with a positive history limit and rejected items prevent the first fetch window from satisfying that limit, get_items() doubles the window and runs _unwrap_valid_items() over the entire suffix again. Emitting the warning inside _unwrap() therefore logs the same plaintext record on every expansion; a plaintext-heavy or attacker-controlled store can generate roughly twice as many warning records as stored items on every model turn, repeatedly consuming logging I/O and storage. Emit a bounded warning once per public read, optionally with a rejection count, rather than once per unwrap attempt.

Useful? React with 👍 / 👎.

@seratch

seratch commented Aug 12, 2026

Copy link
Copy Markdown
Member

Thanks for the thoughtful hardening proposal. The current plaintext passthrough is real, but this PR does not establish a supported scenario where EncryptedSession must treat a writable backing store as an untrusted integrity boundary. The new flag would add a permanent behavior mode while leaving the default unchanged, and it cannot protect against deletion, reordering, or replay by the same store. It also makes pop_item() destructively scan past rejected tail items, which conflicts with one-item pop and retry-rewind ownership. I am going to close this PR.

@seratch seratch closed this Aug 12, 2026
@SashaMIT

Copy link
Copy Markdown
Author

Thanks for the careful read, that makes sense. The pop scan-past behavior was the part I was least sure of, and you're right that a writable store can delete or reorder regardless, so the flag only ever covered a slice of the integrity question. Appreciate the quick review either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants