{"record":{"id":"f984048f9a47deda","repo":"langchain-ai/deepagents","slug":"snapshot-signing-key-must-be-a-non-empty-str-or","errorCode":null,"errorMessage":"`snapshot_signing_key` must be a non-empty str or bytes.","messagePattern":"`snapshot_signing_key` must be a non-empty str or bytes\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/partners/quickjs/langchain_quickjs/_snapshot.py","lineNumber":60,"sourceCode":"\nSnapshotRecord = tuple[str, bytes]\n\n# Domain-separation prefix folded into every signed message so a snapshot HMAC\n# can never be confused with an HMAC computed over some other blob using the\n# same key. Bump the version suffix if the signed-message layout ever changes.\n_HMAC_DOMAIN = b\"langchain-quickjs/snapshot-hmac/v1\"\n\n\ndef normalize_signing_key(key: str | bytes) -> bytes:\n    \"\"\"Coerce a user-supplied signing key into raw ``bytes``.\n\n    ``str`` keys are UTF-8 encoded; ``bytes`` are used verbatim. Empty keys are\n    rejected because an empty HMAC key provides no integrity guarantee.\n    \"\"\"\n    material = key.encode(\"utf-8\") if isinstance(key, str) else bytes(key)\n    if not material:\n        msg = \"`snapshot_signing_key` must be a non-empty str or bytes.\"\n        raise ValueError(msg)\n    return material\n\n\ndef sign_snapshot(key: bytes, payload: bytes, thread_id: str) -> bytes:\n    \"\"\"Return the HMAC-SHA256 tag over a fully materialized snapshot.\n\n    The tag is computed over the *completed materialized* snapshot bytes (the\n    full heap serialization) bound to ``thread_id``, so a valid snapshot for one\n    thread cannot be replayed into another by a state-store adversary. This is\n    signed before the payload is delta-encoded (``encode_snapshot``) and flushed\n    onto the ``bsdiff`` patch chain; verification recomputes the tag over the\n    materialized bytes the chain replays back to.\n    \"\"\"\n    return hmac.new(key, _signed_message(payload, thread_id), sha256).digest()\n\n\ndef verify_snapshot(\n    key: bytes, payload: bytes, thread_id: str, tag: bytes | None","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/partners/quickjs/langchain_quickjs/_snapshot.py#L42-L78","documentation":"`normalize_signing_key` validates the HMAC key used to sign snapshot files. Keys must be a non-empty str or bytes: strs are UTF-8 encoded, bytes are used verbatim, and empty material is rejected because an empty HMAC key provides no integrity guarantee.","triggerScenarios":"Passing `snapshot_signing_key=\"\"` or `b\"\"` (or a falsy bytes-like object, e.g. `bytearray()`) when constructing the middleware or in direct calls to `normalize_signing_key`.","commonSituations":"Reading the signing key from an env var or config file that is empty/unset and passing the empty string through without checking; a template or secret manager returning an empty value; typo'd env var name yielding `''`.","solutions":["Provide a non-empty secret string, e.g. `snapshot_signing_key=os.environ['SNAPSHOT_KEY']` after confirming it is set.","Fail fast at startup: assert the key env var is present and non-empty before building the middleware.","If no signing is intended, omit `snapshot_signing_key` (default None) instead of passing an empty value."],"exampleFix":"// before\nmiddleware = QuickJsMiddleware(snapshot_signing_key=os.environ.get(\"SNAPSHOT_KEY\", \"\"))\n\n// after\nkey = os.environ[\"SNAPSHOT_KEY\"]\nassert key, \"SNAPSHOT_KEY must be set\"\nmiddleware = QuickJsMiddleware(snapshot_signing_key=key)","handlingStrategy":"validation","validationCode":"key = os.environ.get(\"SNAPSHOT_KEY\")\nif not key:\n    raise ValueError(\"SNAPSHOT_KEY must be a non-empty str or bytes\")","typeGuard":"def is_valid_signing_key(key) -> bool:\n    if isinstance(key, str):\n        return bool(key.encode(\"utf-8\"))\n    return isinstance(key, (bytes, bytearray)) and bool(bytes(key))","tryCatchPattern":"try:\n    mw = QuickJsMiddleware(snapshot_signing_key=key)\nexcept ValueError as e:\n    if \"snapshot_signing_key\" in str(e):\n        fix_key_configuration()  # load from secret manager / abort startup","preventionTips":["Load signing keys from a secret manager and fail fast at startup if empty.","Never pass `os.environ.get(..., \"\")` defaults for required secrets.","Omit the parameter entirely when signing is not intended instead of passing an empty value."],"tags":["validation","configuration","hmac","snapshots"],"backgroundTag":"invalid-signing-key","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}