diff --git a/src/langbot/pkg/plugin/certification.py b/src/langbot/pkg/plugin/certification.py index 65488d98b..958f37c50 100644 --- a/src/langbot/pkg/plugin/certification.py +++ b/src/langbot/pkg/plugin/certification.py @@ -242,11 +242,21 @@ def execution_mode_for_persisted_installation( ) -> PluginExecutionMode: """Derive placement only from persisted facts bound to the exact artifact.""" + if len(artifact_digest) != 64 or any(character not in '0123456789abcdef' for character in artifact_digest.lower()): + return PluginExecutionMode.DEDICATED if not isinstance(install_info, Mapping): return PluginExecutionMode.DEDICATED certification = install_info.get('_certification') if not isinstance(certification, Mapping): return PluginExecutionMode.DEDICATED + normalized_digest = certification.get('normalized_digest') + if not isinstance(normalized_digest, str) or len(normalized_digest) != 64: + return PluginExecutionMode.DEDICATED + if any(character not in '0123456789abcdef' for character in normalized_digest.lower()): + return PluginExecutionMode.DEDICATED + certificate_id = certification.get('certificate_id') + if not isinstance(certificate_id, str) or not certificate_id.strip(): + return PluginExecutionMode.DEDICATED shared_facts = { 'artifact_digest': artifact_digest, 'verification': CertificateVerification.VALID.value, diff --git a/tests/integration/plugin/test_certified_plugin_admission.py b/tests/integration/plugin/test_certified_plugin_admission.py index 6106c9248..13b8019de 100644 --- a/tests/integration/plugin/test_certified_plugin_admission.py +++ b/tests/integration/plugin/test_certified_plugin_admission.py @@ -60,6 +60,8 @@ async def test_install_plugin_admits_archive_before_persistence_and_applies_sele assert persisted_info['_certification']['runtime_profile'] == expected_profile assert persisted_info['_certification']['artifact_digest'] == hashlib.sha256(package).hexdigest() assert persisted_info['_certification']['normalized_digest'] == _normalized_digest(package) + if archive_kind == 'signed_shared': + assert persisted_info['_certification']['certificate_id'] == 'ephemeral' connector.handler.apply_plugin_installation.assert_awaited_once_with( binding, artifact_package=package, diff --git a/tests/unit_tests/plugin/test_certified_plugin_policy.py b/tests/unit_tests/plugin/test_certified_plugin_policy.py index 344e45918..9d05cb52a 100644 --- a/tests/unit_tests/plugin/test_certified_plugin_policy.py +++ b/tests/unit_tests/plugin/test_certified_plugin_policy.py @@ -152,57 +152,48 @@ def test_log_visibility_policy_only_scopes_valid_shared_certifications( assert visibility.value == expected_visibility +def _complete_persisted_certification() -> dict[str, object]: + return { + 'artifact_digest': 'a' * 64, + 'normalized_digest': 'b' * 64, + 'verification': 'valid', + 'certificate_runtime_profile': 'shared-runtime-v1', + 'certificate_id': 'ed25519:trusted-issuer', + 'runtime_profile': 'shared-runtime-v1', + 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', + } + + @pytest.mark.parametrize( ('certification', 'expected_mode'), [ - ( - { - 'artifact_digest': 'a' * 64, - 'verification': 'valid', - 'certificate_runtime_profile': 'shared-runtime-v1', - 'runtime_profile': 'shared-runtime-v1', - 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', - }, - PluginExecutionMode.SHARED_CERTIFIED, - ), + (_complete_persisted_certification(), PluginExecutionMode.SHARED_CERTIFIED), (None, PluginExecutionMode.DEDICATED), ({}, PluginExecutionMode.DEDICATED), ( { - 'artifact_digest': 'a' * 64, + **_complete_persisted_certification(), 'verification': 'invalid', - 'certificate_runtime_profile': 'shared-runtime-v1', - 'runtime_profile': 'shared-runtime-v1', - 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', }, PluginExecutionMode.DEDICATED, ), ( { + **_complete_persisted_certification(), 'artifact_digest': 'b' * 64, - 'verification': 'valid', - 'certificate_runtime_profile': 'shared-runtime-v1', - 'runtime_profile': 'shared-runtime-v1', - 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', }, PluginExecutionMode.DEDICATED, ), ( { - 'artifact_digest': 'a' * 64, - 'verification': 'valid', - 'certificate_runtime_profile': 'shared-runtime-v1', + **_complete_persisted_certification(), 'runtime_profile': 'dedicated', - 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', }, PluginExecutionMode.DEDICATED, ), ( { - 'artifact_digest': 'a' * 64, - 'verification': 'valid', - 'certificate_runtime_profile': 'shared-runtime-v1', - 'runtime_profile': 'shared-runtime-v1', + **_complete_persisted_certification(), 'admission_code': 'CERTIFIED_PLUGIN_OSS_FORCED_DEDICATED', }, PluginExecutionMode.DEDICATED, @@ -210,7 +201,7 @@ def test_log_visibility_policy_only_scopes_valid_shared_certifications( ], ) def test_persisted_certification_selects_shared_execution_only_for_exact_admitted_artifact( - certification: dict[str, str] | None, + certification: dict[str, object] | None, expected_mode: PluginExecutionMode, ) -> None: from langbot.pkg.plugin.certification import execution_mode_for_persisted_installation @@ -226,6 +217,97 @@ def test_persisted_certification_selects_shared_execution_only_for_exact_admitte ) +@pytest.mark.parametrize( + 'missing_field', + [ + 'artifact_digest', + 'normalized_digest', + 'verification', + 'certificate_runtime_profile', + 'certificate_id', + 'runtime_profile', + 'admission_code', + ], +) +def test_persisted_certification_requires_every_shared_admission_fact(missing_field: str) -> None: + from langbot.pkg.plugin.certification import execution_mode_for_persisted_installation + + certification = _complete_persisted_certification() + del certification[missing_field] + + assert ( + execution_mode_for_persisted_installation( + artifact_digest='a' * 64, + install_info={'_certification': certification}, + ) + is PluginExecutionMode.DEDICATED + ) + + +@pytest.mark.parametrize( + ('field', 'malformed_value'), + [ + ('artifact_digest', None), + ('artifact_digest', 123), + ('artifact_digest', ''), + ('normalized_digest', None), + ('normalized_digest', 123), + ('normalized_digest', ''), + ('normalized_digest', ' ' * 64), + ('normalized_digest', 'b' * 63), + ('normalized_digest', 'g' * 64), + ('certificate_id', None), + ('certificate_id', 123), + ('certificate_id', ''), + ('certificate_id', ' '), + ('verification', None), + ('verification', 123), + ('verification', ''), + ('certificate_runtime_profile', None), + ('certificate_runtime_profile', 123), + ('certificate_runtime_profile', ''), + ('runtime_profile', None), + ('runtime_profile', 123), + ('runtime_profile', ''), + ('admission_code', None), + ('admission_code', 123), + ('admission_code', ''), + ], +) +def test_persisted_certification_rejects_malformed_shared_admission_fact( + field: str, + malformed_value: object, +) -> None: + from langbot.pkg.plugin.certification import execution_mode_for_persisted_installation + + certification = _complete_persisted_certification() + certification[field] = malformed_value + + assert ( + execution_mode_for_persisted_installation( + artifact_digest='a' * 64, + install_info={'_certification': certification}, + ) + is PluginExecutionMode.DEDICATED + ) + + +@pytest.mark.parametrize('malformed_digest', ['', 'a' * 63, 'g' * 64]) +def test_persisted_certification_rejects_malformed_matching_raw_digest(malformed_digest: str) -> None: + from langbot.pkg.plugin.certification import execution_mode_for_persisted_installation + + certification = _complete_persisted_certification() + certification['artifact_digest'] = malformed_digest + + assert ( + execution_mode_for_persisted_installation( + artifact_digest=malformed_digest, + install_info={'_certification': certification}, + ) + is PluginExecutionMode.DEDICATED + ) + + def _archive_bytes(manifest: dict[str, object]) -> bytes: buffer = io.BytesIO() with zipfile.ZipFile(buffer, 'w') as archive: diff --git a/tests/unit_tests/plugin/test_connector_methods.py b/tests/unit_tests/plugin/test_connector_methods.py index f6ed0ec5d..716b0f1bf 100644 --- a/tests/unit_tests/plugin/test_connector_methods.py +++ b/tests/unit_tests/plugin/test_connector_methods.py @@ -762,8 +762,10 @@ class TestSetPluginConfig: '_artifact_storage': 'tenant_binary_storage_v1', '_certification': { 'artifact_digest': TEST_INSTALLATION_BINDING.artifact_digest, + 'normalized_digest': 'b' * 64, 'verification': 'valid', 'certificate_runtime_profile': 'shared-runtime-v1', + 'certificate_id': 'ed25519:trusted-issuer', 'runtime_profile': 'shared-runtime-v1', 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', }, diff --git a/tests/unit_tests/plugin/test_connector_reconcile.py b/tests/unit_tests/plugin/test_connector_reconcile.py index e3cf319ce..fde178fa2 100644 --- a/tests/unit_tests/plugin/test_connector_reconcile.py +++ b/tests/unit_tests/plugin/test_connector_reconcile.py @@ -52,6 +52,7 @@ def mock_archive_admission(connector: PluginRuntimeConnector, digest: str) -> No 'normalized_digest': digest, 'verification': 'valid', 'certificate_runtime_profile': 'shared-runtime-v1', + 'certificate_id': 'ed25519:trusted-issuer', 'runtime_profile': 'shared-runtime-v1', 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', }, @@ -186,8 +187,10 @@ async def test_reconcile_reload_projects_certified_exact_artifact_to_shared_exec digest, certification={ 'artifact_digest': digest, + 'normalized_digest': 'b' * 64, 'verification': 'valid', 'certificate_runtime_profile': 'shared-runtime-v1', + 'certificate_id': 'ed25519:trusted-issuer', 'runtime_profile': 'shared-runtime-v1', 'admission_code': 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE', },