Skip to main content

Redactions

from jes import Redactions
from jes.redactions import TOKEN_RE

The placeholder tokens of one conversation and the values they replace. Use one store per conversation, and pass it to each check. See Multi-turn conversations.

Redactions(
*,
scope: bytes | None = None, # None: a random in-memory scope
max_entries: int = 1_000,
max_bytes: int = 8_388_608,
)

scope names the conversation, for example f"{tenant}:{conversation_id}".encode(). It must be 1 to 1,024 bytes. A saved store loads only under the same scope. max_entries and max_bytes cap the store and must be positive integers. A bad argument raises RedactionError.

Properties​

NameMeaning
scope_idA SHA-256 hex digest of the scope. Safe to log, and equal for stores of the same conversation.
len(store)The number of entries

dumps​

store.dumps(key: bytes, *, associated_data: bytes) -> bytes

Encrypts the store with AES-256-GCM, using a fresh nonce each time. key must be 32 bytes. associated_data must be 1 to 4,096 bytes, and must match on load. Needs jes[crypto]; without it, dumps raises PolicyError.

loads​

Redactions.loads(
blob: bytes,
key: bytes,
*,
scope: bytes,
associated_data: bytes,
max_entries: int = 1_000,
max_bytes: int = 8_388_608,
) -> Redactions

Decrypts a blob saved by dumps. Needs jes[crypto]. The limits bound the loaded store. It raises RedactionError on a wrong key, scope, or associated data, on a blob that isn't a store or has another version, and on a blob that holds more than the limits allow.

Each blob records its format version, and loads rejects any other version.

TOKEN_RE​

TOKEN_RE = re.compile(r"\[JES_PII_[A-Za-z0-9_-]{22}\]")

Matches a placeholder, for example [JES_PII_3q2-7wEjT0mYb1kXoA9xVw].

Behavior​

  • A placeholder is an HMAC of its value under the store's random secret. The same value always gets the same placeholder in one store, and no other store can produce or restore it.
  • Values leave a store only through check_output restoration and dumps. No API restores arbitrary text.
  • A store can't be copied or pickled: copy.copy, copy.deepcopy, and pickle raise TypeError. Save it with dumps. Its repr shows only the start of the scope id and the entry count.
  • A check stages its new entries and commits them only when the result is complete and allowed. A block or error leaves the store unchanged.
  • A check that would push the store past max_entries or max_bytes blocks with redaction_store_full. More redactions in one check than the guard's Limits(max_redactions=...) block with too_many_redactions. Neither raises.
  • A store is safe to share between threads.
  • Key rotation and replay protection are your responsibility.