feat: Add in-memory signing API returning the Sigstore bundle as bytes#642
Open
DevamShah wants to merge 1 commit into
Open
feat: Add in-memory signing API returning the Sigstore bundle as bytes#642DevamShah wants to merge 1 commit into
DevamShah wants to merge 1 commit into
Conversation
The library only exposed `Config.sign(model_path, signature_path)`, which forces signing output to a filesystem path. Serverless functions, CI steps, and streaming pipelines often run on read-only or ephemeral filesystems and need the Sigstore bundle in memory, leaving a write-read-delete temp-file workaround as the only option. Add an abstract `Signature.to_bytes()` and implement it on both Sigstore signature types as the UTF-8 encoded bundle JSON, then route `write()` through it so the on-disk and in-memory artifacts share a single serialization path. Expose `Config.sign_to_bytes()` and a module-level `sign_to_bytes()` helper that mirror the existing `sign()` API. The hash, payload, and sign sequence is factored into a private `_sign()` helper reused by both output modes; `sign()` is behaviorally unchanged. Document the new API in the README and add a CHANGELOG entry. Add a key-based API test asserting the returned bytes are a valid Sigstore bundle whose signed payload matches what `sign()` writes to disk. Part of: sigstore#582 Signed-off-by: Devam Shah <devamshah91@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
Config.sign_to_bytes()(and a module-levelsigning.sign_to_bytes()helper) so callers can obtain the Sigstore bundle in memory as bytes instead of being forced to write it to disk, enabling serverless and pipeline signing flows. The existing disk-writingsign()remains the default and is unchanged for callers. This is the library half of #582; the--stdoutCLI flag also requested in that issue is a natural follow-up that builds directly on this API, so this PR is taggedPart of: #582rather than closing it.Problem
Today the only way to capture a signature through the library API is
Config.sign(model_path, signature_path), which serializes the bundle and writes it to a filesystem path. In serverless functions (read-only or ephemeral filesystems), CI/CD steps, and streaming pipelines, the caller wants the bundle bytes directly -- to push to an object store, attach to an HTTP response, or hand to another process -- without round-tripping through a temp file. The workaround (write to a temp file, read it back, delete it) adds filesystem dependencies, cleanup burden, and a small race/leak surface for what is sensitive signing output. This is the gap raised in #582 and requested by the maintainer.Change
Following the existing API conventions:
model_signing/_signing/signing.py: added an abstractto_bytes(self) -> bytesmethod to theSignaturebase class, mirroring the existingwrite/readcontract.model_signing/_signing/sign_sigstore.pyandsign_sigstore_pb.py: implementedto_bytes()asself.bundle.to_json().encode("utf-8"), and refactoredwrite()to delegate to it (path.write_bytes(self.to_bytes())) so the on-disk and in-memory representations are guaranteed identical and produced by a single code path. These are the only two concreteSignaturesubclasses; the elliptic-key and PKCS#11 signers both returnsign_sigstore_pb.Signature, so no subclass is left without an implementation.model_signing/signing.py: extracted the hash -> payload -> sign sequence shared by both output modes into a private_sign()helper, then addedConfig.sign_to_bytes(model_path) -> bytesand a module-levelsign_to_bytes(model_path)convenience function that mirrors the existingsign()function.sign()now reuses_sign()and is behaviorally unchanged.README.md: added a usage snippet forsign_to_bytes()in the Model Signing API section, alongside the existingsign()examples.tests/api_test.py: addedTestKeySigning::test_sign_to_bytes. It asserts the returned value isbytes, that nothing is written to disk, that the bytes parse as a valid Sigstore bundle with the expected signed resources, and that the in-memory DSSE payload is identical to whatsign()writes to disk.CHANGELOG.md: added an entry underUnreleased / Added.The change is purely additive and backwards compatible -- no existing signature, default, or output is altered.
Security rationale
The bundle returned by
to_bytes()is byte-for-byte the same Sigstore bundle thatwrite()persists -- both now flow through the singleto_bytes()serializer, so there is no divergence between the in-memory and on-disk signed artifact, and no second serialization path to audit. Keeping signing output in memory removes an unnecessary plaintext-on-disk step for sensitive provenance material, which is desirable on shared or ephemeral build hosts and reduces residual-file cleanup obligations (cf. CWE-459 Incomplete Cleanup, CWE-212 Improper Removal of Sensitive Information, CWE-200). This strengthens ML supply-chain integrity -- the project's core purpose, aligned with SLSA provenance and OWASP ML Security Top 10 ML06 (AI Supply Chain Attacks) -- by making it practical to sign in environments that previously could not write to disk, rather than encouraging the insecure write-read-delete workaround. No new key material handling, network behavior, or trust decision is introduced; the signer, payload, and bundle format are unchanged.Testing
Validated against a clean clone of
mainwith the package installed editable in a fresh venv:ruff format --check src/-- pass.ruff check src/--All checks passed!.pytest tests/api_test.py -m "not integration"--4 passed, 2 deselected, including the newtest_sign_to_bytes.pkcs11extra) --196 passed; the only 2 failures (scripts/tests/verify_test.pyv0.3.1 / v1.0.0 fixtures) reproduce identically on unmodifiedmainand are unrelated to this change.The new test uses local elliptic-key signing, so it runs offline with no Sigstore/OIDC network dependency. Because the elliptic-key signer returns
sign_sigstore_pb.Signature, this offline test also exercises the newto_bytes()implementation directly.Part of: #582
Checklist