From 16205e810a6a9edb47c0db23d74466239c36876d Mon Sep 17 00:00:00 2001 From: disqualifier Date: Sun, 28 Jun 2026 17:18:28 -0400 Subject: [PATCH] fix: deepcopy in reencrypt/decrypt_record so input is not mutated both used record.copy() (shallow), leaving unencrypted mutable fields shared between the input and the returned dict, violating the documented 'input is not mutated' contract. switched to copy.deepcopy. Signed-off-by: disqualifier --- README.md | 4 ++-- src/envelope_crypto/envelope_crypto.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3620fbc..8f91801 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ and storage-agnostic. `requirements.txt`: ``` -envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.0 +envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.1 ``` Direct: ```bash -pip install "envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.0" +pip install "envelope_crypto @ git+ssh://git@git.rethinkstudios.io/rethink-public/envelope_crypto.git@v0.1.1" ``` Requires `cryptography` (pulled transitively). diff --git a/src/envelope_crypto/envelope_crypto.py b/src/envelope_crypto/envelope_crypto.py index 70f36a8..f323526 100644 --- a/src/envelope_crypto/envelope_crypto.py +++ b/src/envelope_crypto/envelope_crypto.py @@ -54,6 +54,7 @@ function variants are the same functions — use whichever fits your storage. """ import os +import copy import json import base64 import hashlib @@ -313,7 +314,7 @@ class EnvelopeCrypto: if not self.master_key: raise ValueError("destination not initialized with data key") - result = record.copy() + result = copy.deepcopy(record) for key, value in record.items(): if isinstance(value, dict) and value.get("secure") is True and "iv" in value and "data" in value: result[key] = self.encrypt_data(source_crypto.decrypt_data(value)) @@ -361,7 +362,7 @@ def decrypt_record(crypto: EnvelopeCrypto, record, traversal_level: int = 2) -> if not isinstance(record, dict): return record - result = record.copy() + result = copy.deepcopy(record) for key, value in record.items(): if isinstance(value, dict) and value.get("secure") is True and "iv" in value and "data" in value: try: