From 071813a6bd633dcf58bb4f69434354f85cc571f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 05:34:58 -0800 Subject: [PATCH 1/7] build(deps): bump sigstore-conformance/extremely-dangerous-public-oidc-beacon (#598) Bumps the all group with 1 update: [sigstore-conformance/extremely-dangerous-public-oidc-beacon](https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon). Updates `sigstore-conformance/extremely-dangerous-public-oidc-beacon` from 9775b7374737339e046064d8e5a4bbf4b00565a4 to 1e3cabecd3790f48b79a795424e12fa3cb880dcb - [Commits](https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/compare/9775b7374737339e046064d8e5a4bbf4b00565a4...1e3cabecd3790f48b79a795424e12fa3cb880dcb) --- updated-dependencies: - dependency-name: sigstore-conformance/extremely-dangerous-public-oidc-beacon dependency-version: 1e3cabecd3790f48b79a795424e12fa3cb880dcb dependency-type: direct:production dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cross_os.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cross_os.yml b/.github/workflows/cross_os.yml index 4587346f..820533fe 100644 --- a/.github/workflows/cross_os.yml +++ b/.github/workflows/cross_os.yml @@ -75,7 +75,7 @@ jobs: - name: Set up Hatch uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install - name: store beacon token into oidc-token.txt - uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@9775b7374737339e046064d8e5a4bbf4b00565a4 # main + uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@1e3cabecd3790f48b79a795424e12fa3cb880dcb # main - name: Sign the model run: hatch run python -m model_signing sign sigstore model_root/ --use_staging --signature model.sig --identity_token $(cat oidc-token.txt) - name: upload model signature From 5e96a3508eff62918188c54dcaacf44a07777afd Mon Sep 17 00:00:00 2001 From: Aleks <121458075+SequeI@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:57:27 +0000 Subject: [PATCH 2/7] fix: remove relative link for full URL to image resource (#599) Signed-off-by: SequeI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0428203b..90b83b48 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Model signers should monitor for occurences of their signing identity in the log. Sigstore is actively developing a [log monitor](https://github.com/sigstore/rekor-monitor) that runs on GitHub Actions. -![Signing models with Sigstore](docs/images/sigstore-model-diagram.png) +![Signing models with Sigstore](https://raw.githubusercontent.com/sigstore/model-transparency/main/docs/images/sigstore-model-diagram.png) ### Model Signing CLI From 68e7efe38198c09492e442cdee6bbda0fc36e52e Mon Sep 17 00:00:00 2001 From: Puerco Date: Wed, 14 Jan 2026 11:51:35 -0600 Subject: [PATCH 3/7] Digest subcommand (#566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add digest subcommand Signed-off-by: Adolfo García Veytia (Puerco) * Update README with digest subcommand info Signed-off-by: Adolfo García Veytia (Puerco) * Add digest entry to changelog Signed-off-by: Adolfo García Veytia (Puerco) --------- Signed-off-by: Adolfo García Veytia (Puerco) --- CHANGELOG.md | 2 +- README.md | 9 ++++++++ src/model_signing/_cli.py | 45 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc2dc17..0f4ce652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ All versions prior to 1.0.0 are untracked. ## [Unreleased] ### Added -- ... +-Added the `digest` subcommand to compute and print a model's digest. This enables other tools to easily pair the attestations with a model directory. ### Changed - ... diff --git a/README.md b/README.md index 90b83b48..cbfe4a2c 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,15 @@ This will open an OIDC flow to obtain a short lived token for the certificate. The identity used during signing and the provider must be reused during verification. +To only compute and output the digest of the model, you can use the `digest` +subcommand, pointing it to the model directory: + +```bash +[...]$ model_signing digest bert-base-uncased +``` + +The digest subcommand follows the same ignore rules used when signing. + ## Using Private Sigstore Instances To use a private Sigstore setup (e.g. custom Rekor/Fulcio), use the `--trust_config` flag: diff --git a/src/model_signing/_cli.py b/src/model_signing/_cli.py index 2d90f135..79a04b79 100644 --- a/src/model_signing/_cli.py +++ b/src/model_signing/_cli.py @@ -263,6 +263,51 @@ def main(log_level: str) -> None: sys.exit(1) +@main.command(name="digest") +@_model_path_argument +@_ignore_paths_option +@_ignore_git_paths_option +@_allow_symlinks_option +def _digest( + model_path: pathlib.Path, + ignore_paths: Iterable[pathlib.Path], + ignore_git_paths: bool, + allow_symlinks: bool, +) -> None: + """Computes the digest of a model. + + The digest subcommand serializes a model directory and computes the "root" + digest (hash), the same used when signing and as the attestation subject. + + By default, git-related files are ignored (same behavior as the sign + command). Use --no-ignore-git-paths to include them. To ignore other + files from the directory serialization, use --ignore-paths. + """ + from model_signing._hashing import memory + + try: + # First, generate the manifest of the model directory + ignored = _resolve_ignore_paths(model_path, list(ignore_paths)) + manifest = ( + model_signing.hashing.Config() + .set_ignored_paths(paths=ignored, ignore_git_paths=ignore_git_paths) + .set_allow_symlinks(allow_symlinks) + .hash(model_path) + ) + + # Then, hash the resource descriptors as done when signing + hasher = memory.SHA256() + for descriptor in manifest.resource_descriptors(): + hasher.update(descriptor.digest.digest_value) + root_digest = hasher.compute() + + click.echo(f"{root_digest.algorithm}:{root_digest.digest_hex}") + + except Exception as err: + click.echo(f"Computing digest failed: {err}", err=True) + sys.exit(1) + + @main.group(name="sign", subcommand_metavar="PKI_METHOD", cls=_PKICmdGroup) def _sign() -> None: """Sign models. From 3c805a7eb752966ad0946f64cbaf5ea26226d8fd Mon Sep 17 00:00:00 2001 From: Aleks <121458075+SequeI@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:23:56 +0000 Subject: [PATCH 4/7] ci/cd: adding tests for custom trust config (#572) * ci/cd: adding tests for custom trust config Signed-off-by: SequeI * fix: post-review changes Signed-off-by: SequeI --------- Signed-off-by: SequeI --- src/model_signing/signing.py | 2 +- tests/_signing/sigstore_test.py | 88 +++++++++ .../testdata/custom_trust_config.json | 175 ++++++++++++++++++ tests/api_test.py | 46 +++++ 4 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 tests/_signing/testdata/custom_trust_config.json diff --git a/src/model_signing/signing.py b/src/model_signing/signing.py index e77ac900..4de79be2 100644 --- a/src/model_signing/signing.py +++ b/src/model_signing/signing.py @@ -101,7 +101,7 @@ def sign( model_path: The path to the model to sign. signature_path: The path of the resulting signature. """ - if not self._signer: + if self._signer is None: self.use_sigstore_signer() manifest = self._hashing_config.hash(model_path) payload = signing.Payload(manifest) diff --git a/tests/_signing/sigstore_test.py b/tests/_signing/sigstore_test.py index 68dbd26f..40619bd4 100644 --- a/tests/_signing/sigstore_test.py +++ b/tests/_signing/sigstore_test.py @@ -332,3 +332,91 @@ def test_verify_not_intoto_statement( with pytest.raises(ValueError, match="Expected in-toto .* payload"): self._verify_dsse_signature(signature_path) + + def test_sign_with_custom_trust_config( + self, + sample_model_folder, + mocked_oidc_provider, + mocked_sigstore_signer, + mocked_sigstore_models, + tmp_path, + ): + trust_config_path = ( + pathlib.Path(__file__).parent + / "testdata" + / "custom_trust_config.json" + ) + + serializer = file.Serializer( + self._file_hasher_factory, allow_symlinks=True + ) + manifest = serializer.serialize(sample_model_folder) + signature_path = tmp_path / "model.sig" + + mocked_client_trust_config = mocked_sigstore_models["ClientTrustConfig"] + mocked_custom_config = mock.MagicMock() + mocked_client_trust_config.from_json.return_value = mocked_custom_config + + signer = sigstore.Signer( + use_staging=False, trust_config=trust_config_path + ) + payload = signing.Payload(manifest) + signature = signer.sign(payload) + signature.write(signature_path) + + assert mocked_client_trust_config.from_json.called + call_args = mocked_client_trust_config.from_json.call_args + assert call_args is not None + assert isinstance(call_args[0][0], str) + trust_config_content = json.loads(call_args[0][0]) + assert trust_config_content["mediaType"] == ( + "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" + ) + assert "signing_config" in trust_config_content + assert "trustedRoot" in trust_config_content + + def test_verify_with_custom_trust_config( + self, + sample_model_folder, + mocked_oidc_provider, + mocked_sigstore_signer, + mocked_sigstore_models, + mocked_sigstore_verifier, + tmp_path, + ): + trust_config_path = ( + pathlib.Path(__file__).parent + / "testdata" + / "custom_trust_config.json" + ) + + serializer = file.Serializer( + self._file_hasher_factory, allow_symlinks=True + ) + manifest = serializer.serialize(sample_model_folder) + signature_path = tmp_path / "model.sig" + self._sign_manifest(manifest, signature_path, sigstore.Signer) + + mocked_client_trust_config = mocked_sigstore_models["ClientTrustConfig"] + mocked_custom_config = mock.MagicMock() + mocked_client_trust_config.from_json.return_value = mocked_custom_config + + verifier = sigstore.Verifier( + identity="test", + oidc_issuer="test", + use_staging=False, + trust_config=trust_config_path, + ) + signature = sigstore.Signature.read(signature_path) + verifier.verify(signature) + + assert mocked_client_trust_config.from_json.called + call_args = mocked_client_trust_config.from_json.call_args + assert call_args is not None + assert isinstance(call_args[0][0], str) + trust_config_content = json.loads(call_args[0][0]) + assert trust_config_content["mediaType"] == ( + "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" + ) + assert "signing_config" in trust_config_content + assert "trustedRoot" in trust_config_content diff --git a/tests/_signing/testdata/custom_trust_config.json b/tests/_signing/testdata/custom_trust_config.json new file mode 100644 index 00000000..c021d799 --- /dev/null +++ b/tests/_signing/testdata/custom_trust_config.json @@ -0,0 +1,175 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] + }, + "signing_config": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstore.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2023-04-14T21:38:40Z" + }, + "operator": "example.com" + }, + { + "url": "https://fulcio-old.example.com", + "majorApiVersion": 1, + "validFor": { + "start": "2022-04-14T21:38:40Z", + "end": "2023-04-14T21:38:40Z" + }, + "operator": "example.com" + } + ], + "oidcUrls": [ + { + "url": "https://oauth2.sigstore.dev/auth", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-16T00:00:00Z" + }, + "operator": "example.com" + } + ], + "rekorTlogUrls": [ + { + "url": "https://rekor.sigstore.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2021-01-12T11:53:27Z" + }, + "operator": "example.com" + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstore.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + }, + "operator": "example.com" + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + } +} diff --git a/tests/api_test.py b/tests/api_test.py index fda2857e..1195d9f1 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -182,6 +182,52 @@ def test_sign_and_verify( sample_model_folder ) + @pytest.mark.integration + def test_sign_and_verify_with_custom_trust_config( + self, sigstore_oidc_beacon_token, sample_model_folder, tmp_path + ): + trust_config_path = ( + Path(__file__).parent + / "_signing" + / "testdata" + / "custom_trust_config.json" + ) + + sc = signing.Config() + sc.use_sigstore_signer( + use_staging=False, + identity_token=sigstore_oidc_beacon_token, + trust_config=trust_config_path, + ) + signature_path = tmp_path / "model.sig" + sc.sign(sample_model_folder, signature_path) + + expected_identity = "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" + expected_oidc_issuer = "https://token.actions.githubusercontent.com" + verifying.Config().use_sigstore_verifier( + identity=expected_identity, + oidc_issuer=expected_oidc_issuer, + use_staging=False, + trust_config=trust_config_path, + ).verify(sample_model_folder, signature_path) + + assert get_signed_files(signature_path) == [ + "d0/f00", + "d0/f01", + "d0/f02", + "d1/f10", + "d1/f11", + "d1/f12", + "f0", + "f1", + "f2", + "f3", + ] + check_ignore_paths(signature_path, True, []) + assert get_model_name(signature_path) == os.path.basename( + sample_model_folder + ) + class TestKeySigning: def test_sign_and_verify(self, base_path, populate_tmpdir): From 6a490862470184eb69113c8a9e9a5538db331642 Mon Sep 17 00:00:00 2001 From: Aleks <121458075+SequeI@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:48:48 +0000 Subject: [PATCH 5/7] fix: standardize CLI flags to use hyphens (#600) Use hyphens instead of underscores for all CLI flags (e.g., --trust-config instead of --trust_config). Underscore variants still accepted via token_normalize_func for backwards compatibility. Signed-off-by: SequeI --- CHANGELOG.md | 2 +- README.md | 16 ++++++------- src/model_signing/_cli.py | 49 +++++++++++++++++++++------------------ 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f4ce652..c81b18a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ All versions prior to 1.0.0 are untracked. -Added the `digest` subcommand to compute and print a model's digest. This enables other tools to easily pair the attestations with a model directory. ### Changed -- ... +- Standardized CLI flags to use hyphens (e.g., `--trust-config` instead of `--trust_config`). Underscore variants are still accepted for backwards compatibility via token normalization. ### Fixed - Fixed a bug where ignored symlinks could raise `ValueError`s if allow_symlinks was unset, even though they were skipped during serialization. ([#550](https://github.com/sigstore/model-transparency/pull/550)) diff --git a/README.md b/README.md index cbfe4a2c..f1417a21 100644 --- a/README.md +++ b/README.md @@ -127,10 +127,10 @@ The digest subcommand follows the same ignore rules used when signing. ## Using Private Sigstore Instances -To use a private Sigstore setup (e.g. custom Rekor/Fulcio), use the `--trust_config` flag: +To use a private Sigstore setup (e.g. custom Rekor/Fulcio), use the `--trust-config` flag: ```bash -[...]$ model_signing sign bert-base-uncased --trust_config client_trust_config.json +[...]$ model_signing sign bert-base-uncased --trust-config client_trust_config.json ``` For verification: @@ -138,9 +138,9 @@ For verification: ```bash [...]$ model_signing verify bert-base-uncased \ --signature model.sig \ - --trust_config client_trust_config.json + --trust-config client_trust_config.json --identity "$identity" - --identity_provider "$oidc_provider" + --identity-provider "$oidc_provider" ``` The `client_trust_config.json` file should include: @@ -162,7 +162,7 @@ generate the key pair: And then we use the private key to sign. ```bash -[...]$ model_signing sign key bert-base-uncased --private_key key.priv +[...]$ model_signing sign key bert-base-uncased --private-key key.priv ``` All signing methods support changing the signature name and location via the @@ -182,7 +182,7 @@ model we use [...]$ model_signing verify bert-base-uncased \ --signature model.sig \ --identity "$identity" \ - --identity_provider "$oidc_provider" + --identity-provider "$oidc_provider" ``` Where `$identity` and `$oidc_provider` are those set up during the signing flow @@ -210,7 +210,7 @@ Similarly, for key verification, we can use ```bash [...]$ model_signing verify key bert-base-uncased \ - --signature resnet.sig --public_key key.pub + --signature resnet.sig --public-key key.pub ``` #### Signing with PKCS #11 URIs @@ -252,7 +252,7 @@ the PKCS #11 device and store it in a file in PEM format. With can then use: ```bash [...]$ model_signing verify key --signature model.sig\ - --public_key key.pub /path/to/your/model + --public-key key.pub /path/to/your/model ``` #### OpenTelemetry Support diff --git a/src/model_signing/_cli.py b/src/model_signing/_cli.py index 79a04b79..9a14c009 100644 --- a/src/model_signing/_cli.py +++ b/src/model_signing/_cli.py @@ -69,7 +69,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option for the custom trust configuration. _trust_config_option = click.option( - "--trust_config", + "--trust-config", type=pathlib.Path, metavar="TRUST_CONFIG_PATH", help="The client trust configuration to use", @@ -95,7 +95,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to ignore all unsigned files _ignore_unsigned_files_option = click.option( - "--ignore_unsigned_files/--no-ignore_unsigned_files", + "--ignore-unsigned-files/--no-ignore-unsigned-files", type=bool, show_default=True, help="Ignore all files that were not originally signed.", @@ -104,7 +104,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to set the path to the private key # (when using non-Sigstore PKI). _private_key_option = click.option( - "--private_key", + "--private-key", type=pathlib.Path, metavar="PRIVATE_KEY", required=True, @@ -113,7 +113,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to set a PKCS #11 URI _pkcs11_uri_option = click.option( - "--pkcs11_uri", + "--pkcs11-uri", type=str, metavar="PKCS11_URI", required=True, @@ -123,7 +123,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to pass a certificate chain to # establish root of trust (when signing or verifying using certificates). _certificate_root_of_trust_option = click.option( - "--certificate_chain", + "--certificate-chain", type=pathlib.Path, metavar="CERTIFICATE_PATH", multiple=True, @@ -133,7 +133,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to use Sigstore's staging instance. _sigstore_staging_option = click.option( - "--use_staging", + "--use-staging", type=bool, is_flag=True, help="Use Sigstore's staging instance.", @@ -141,7 +141,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to pass the signing key's certificate _signing_certificate_option = click.option( - "--signing_certificate", + "--signing-certificate", type=pathlib.Path, metavar="CERTIFICATE_PATH", required=True, @@ -150,7 +150,7 @@ def set_attribute(self, key, value): # Decorator for the commonly used option to allow symlinks _allow_symlinks_option = click.option( - "--allow_symlinks", + "--allow-symlinks", is_flag=True, help="Whether to allow following symlinks when signing or verifying files.", ) @@ -216,7 +216,10 @@ def resolve_command( @click.group( - context_settings=dict(help_option_names=["-h", "--help"]), + context_settings=dict( + help_option_names=["-h", "--help"], + token_normalize_func=lambda x: x.replace("_", "-"), + ), epilog=( "Check https://sigstore.github.io/model-transparency for " "documentation and more details." @@ -332,13 +335,13 @@ def _sign() -> None: @_sigstore_staging_option @_trust_config_option @click.option( - "--use_ambient_credentials", + "--use-ambient-credentials", type=bool, is_flag=True, help="Use credentials from ambient environment.", ) @click.option( - "--identity_token", + "--identity-token", type=str, metavar="TOKEN", help=( @@ -347,7 +350,7 @@ def _sign() -> None: ), ) @click.option( - "--oauth_force_oob", + "--oauth-force-oob", is_flag=True, default=False, help=( @@ -356,13 +359,13 @@ def _sign() -> None: ), ) @click.option( - "--client_id", + "--client-id", type=str, metavar="ID", help="The custom OpenID Connect client ID to use during OAuth2", ) @click.option( - "--client_secret", + "--client-secret", type=str, metavar="SECRET", help="The custom OpenID Connect client secret to use during OAuth2", @@ -391,20 +394,20 @@ def _sign_sigstore( taken from an interactive OIDC flow, but ambient credentials could be used to use workload identity tokens (e.g., when running in GitHub actions). Alternatively, a constant identity token can be provided via - `--identity_token`. + `--identity-token`. Sigstore allows users to use a staging instance for test-only signatures. - Passing the `--use_staging` flag would use that instance instead of the + Passing the `--use-staging` flag would use that instance instead of the production one. Additionally, you can specify a custom trust configuration JSON file using - the `--trust_config` flag. This allows you to fully customize the PKI + the `--trust-config` flag. This allows you to fully customize the PKI (Private Key Infrastructure) used in the signing process. By providing a - `--trust_config`, you can define your own transparency logs, certificate + `--trust-config`, you can define your own transparency logs, certificate authorities, and other trust settings, enabling full control over the trust model, including which PKI to use for signature verification. - If `--trust_config` is not provided, the default Sigstore instance is + If `--trust-config` is not provided, the default Sigstore instance is used, which is pre-configured with Sigstore’s own trusted transparency logs and certificate authorities. This provides a ready-to-use default trust model for most use cases but may not be suitable for custom or @@ -668,7 +671,7 @@ def _verify() -> None: subcommand). To enable verification with custom PKI configurations, use the - `--trust_config` option. This allows you to specify your own set of trusted + `--trust-config` option. This allows you to specify your own set of trusted public keys, transparency logs, and certificate authorities for verifying the signature. If not provided, the default Sigstore instance and its associated public keys, logs, and authorities are used. @@ -693,7 +696,7 @@ def _verify() -> None: help="The expected identity of the signer (e.g., name@example.com).", ) @click.option( - "--identity_provider", + "--identity-provider", type=str, metavar="IDENTITY_PROVIDER", required=True, @@ -761,7 +764,7 @@ def _verify_sigstore( @_ignore_git_paths_option @_allow_symlinks_option @click.option( - "--public_key", + "--public-key", type=pathlib.Path, metavar="PUBLIC_KEY", required=True, @@ -818,7 +821,7 @@ def _verify_private_key( @_allow_symlinks_option @_certificate_root_of_trust_option @click.option( - "--log_fingerprints", + "--log-fingerprints", type=bool, is_flag=True, default=False, From b42a5232e6438fe1e4370fe537533a7585a60b21 Mon Sep 17 00:00:00 2001 From: Spencer Schrock Date: Fri, 16 Jan 2026 12:39:43 -0700 Subject: [PATCH 6/7] lint: prevent CLI flags with underscores (#601) This is a follow up of 6a49086, with the intention of preventing new flags from being introduced which violate our now unified style. Signed-off-by: Spencer Schrock --- .github/workflows/lint.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5e50e30b..3504fef8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -100,3 +100,20 @@ jobs: run: hatch fmt --check env: RUFF_OUTPUT_FORMAT: github + + cli-flag-lint: + runs-on: ubuntu-latest + name: CLI Lint + permissions: + contents: read + steps: + - name: Check out source repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Check for CLI flags with underscores + run: | + if grep --recursive --line-number --extended-regexp --include="*.py" '"--[a-zA-Z0-9-]+_[a-zA-Z0-9-]+' src; then + echo "::error::Found CLI flags with underscores. Please use dashes." + exit 1 + fi From 8d4597157ffc4e22eb65c68fceee1420b07cfae2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:27:12 -0800 Subject: [PATCH 7/7] build(deps): bump github/codeql-action in the all group (#604) Bumps the all group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.31.9 to 4.31.10 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5d4e8d1aca955e8d8589aabd499c5cae939e33c7...cdefb33c0f6224e58673d9004f47f7cb3e328b89) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6279f505..72f3fb1e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -54,7 +54,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + uses: github/codeql-action/autobuild@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -81,6 +81,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index ca308390..eb88e0d9 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -82,6 +82,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: sarif_file: results.sarif