security: add opt-in HMAC-SHA256 integrity verification for bundle objects - #21917
security: add opt-in HMAC-SHA256 integrity verification for bundle objects#21917theluckystrike wants to merge 3 commits into
Conversation
…jects Add cryptographic signing to the bundle serialization pipeline via a new PREFECT_BUNDLE_SIGNING_KEY environment variable: - When set, _serialize_bundle_object() prepends an HMAC-SHA256 hex digest to the serialized payload: "<hex_sig>:<base64_payload>" - When set, _deserialize_bundle_object() verifies the signature using hmac.compare_digest() before calling cloudpickle.loads() - When unset (default), behavior is identical to today — fully backwards compatible This gives operators an explicit integrity-verification layer for deployments where the worker operator and the bundle author sit in different trust domains. No new dependencies — uses stdlib hmac and hashlib. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
desertaxle
left a comment
There was a problem hiding this comment.
Thanks for the PR @theluckystrike! I think it'd be better to serialize the entire bundle and include the signature as an additional field in the bundle. That will extend the safety added by this fix to the dependencies also. Would you also be willing to add tests for this PR also?
Restructured HMAC-SHA256 signing per reviewer feedback: - Sign the entire serialized bundle JSON instead of individual fields - Include signature as additional 'signature' field in the bundle dict - This extends integrity protection to dependencies and all other fields - Reverted _serialize/_deserialize_bundle_object to original simple form - Added comprehensive test suite for signing and verification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hi Alex,
Regarding the security improvements in PR #21917, is it possible to receive
bug bounty compensation for this contribution?
Thank you,
Michael
…On Tue, May 12, 2026 at 9:19 PM Alex Streed ***@***.***> wrote:
***@***.**** requested changes on this pull request.
Thanks for the PR @theluckystrike <https://github.com/theluckystrike>! I
think it'd be better to serialize the entire bundle and include the
signature as an additional field in the bundle. That will extend the safety
added by this fix to the dependencies also. Would you also be willing to
add tests for this PR also?
—
Reply to this email directly, view it on GitHub
<#21917 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AMFLKPEE77NSRHYLOWFICPD42MXG3AVCNFSM6AAAAACY2Y5K4WVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHM2DENZTGEYTONBZGE>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Replace RST-style double backticks with plain text in docstrings. Also fix "behaviour" → "behavior" (US spelling per codespell). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Pre-commit fix pushed (removed double backticks from docstrings, fixed UK→US spelling). All local checks passing — ruff, codespell, format. Ready for re-review when CI is green. Happy to make any further adjustments. |
|
Hi @desertaxle, thanks for the feedback! Just wanted to clarify — the current implementation already does exactly what you described: Signs the entire bundle, signature stored as a field: def _sign_bundle(bundle: SerializedBundle) -> None:
content = {k: v for k, v in bundle.items() if k != "signature"}
payload = json.dumps(content, sort_keys=True).encode("utf-8")
bundle["signature"] = hmac.new(signing_key, payload, hashlib.sha256).hexdigest()The HMAC covers all fields ( Tests are included in
The single CI failure (Python 3.14 + postgres:14) is a pre-existing infrastructure issue — Happy to adjust the approach if you had a different architecture in mind. Would you like me to change anything? |
|
After digging further into the bundle integrity model, we think this needs a broader design, and iterating on a PR like this isn't the best place to do that. In particular, the right solution needs to account for where bundle integrity metadata is stored, how workers retrieve trusted expected values, how to verify sidecar bundles, and how to leave room for future asymmetric signing. That likely needs changes across the bundle execution protocol and server/worker boundaries rather than a local HMAC check in bundle serialization. I’m going to close this PR so the Prefect maintainers can take the design and implementation forward. We appreciate you raising this finding! |
Summary
Adds opt-in HMAC-SHA256 signing and verification to
_serialize_bundle_object()and_deserialize_bundle_object()via a newPREFECT_BUNDLE_SIGNING_KEYenvironment variable. When the key is set, serialized bundles are prepended with an HMAC-SHA256 hex digest; deserialization verifies that digest before callingcloudpickle.loads(). When the key is absent, behavior is identical to today — fully backwards compatible.Motivation
This change was suggested by the Prefect security team as a hardening direction for deployments where the worker operator and the bundle author sit in different trust domains. Submitting at their invitation to give operators an explicit integrity-verification layer on top of cloudpickle deserialization.
Changes
src/prefect/bundles/__init__.py:New helper
_get_bundle_signing_key()— readsPREFECT_BUNDLE_SIGNING_KEYfrom the environment. Returns empty bytes when unset (signing disabled).Modified
_serialize_bundle_object()— when a signing key is configured, computeshmac.new(key, payload, sha256).hexdigest()and prepends it to the payload separated by:.Modified
_deserialize_bundle_object()— when a signing key is configured::separator) with a descriptive errorhmac.compare_digest()(timing-safe comparison)cloudpickle.loads()after verification passesBackwards Compatibility
Strictly additive:
PREFECT_BUNDLE_SIGNING_KEYis unset by default — all existing deployments continue to work without modification<64-char hex sig>:<payload>) is only introduced when the key is explicitly configuredhmacandhashlib)Testing Plan
ValueErrorbeforecloudpickle.loads()ValueErrorValueErrorhmac.compare_digest()is used (not==)References
hmac.compare_digest