fix(plugin): keep certified marketplace packages installable on OSS

The certified-archive admission gate treated any declared certificate it could
not resolve as an untrusted archive and rejected the install with
CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED. OSS ships an empty
plugin.certification.trusted_public_keys ring and the marketplace signs every
package with its own issuer key, so all certified marketplace packages (for
example langbot-team/RunnerDemo and langbot-team/LocalAgent) failed at the
'validating plugin package' step before artifact storage.

Admission now distinguishes an unresolvable declaration from a configured trust
decision that fails:

- record certificate_id only when the key_id is actually present in the
  configured ring, so an empty ring yields an unresolvable declaration;
- OSS degrades an unresolvable declaration to the existing oss_dev dedicated
  profile with CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED instead of blocking;
- a resolvable declaration that still fails keeps requiring the explicit
  administrator force (CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED);
- Cloud stays fail-closed and rejects before storage.

No shared-runtime privilege is granted when the issuer is not trusted, so this
withholds isolation rather than escalating it.
This commit is contained in:
TyperBody
2026-09-25 19:32:54 +08:00
parent 262980d612
commit afe700f1d8
4 changed files with 188 additions and 19 deletions
+32 -2
View File
@@ -30,6 +30,19 @@ keys, not private/signing keys. An invalid key-ring configuration is rejected
rather than weakening verification. Keep active issuer keys during a rotation
until archives signed by retired IDs are no longer installed.
The ring may also be supplied out-of-band, which is how hosted deployments
provision it:
```bash
PLUGIN__CERTIFICATION__TRUSTED_PUBLIC_KEYS_JSON='{"ed25519:issuer":"<base64>"}'
```
An **empty** ring is a supported state, not a misconfiguration. OSS defaults to
it, so a self-hosted instance that has not provisioned any issuer key still
installs packages (see the admission matrix below). Configure the ring to grant
the shared-runtime profile; leave it empty to keep every package on the
dedicated profile.
## Admission matrix
| Deployment | SDK verification | Explicit `administrator_force` | Result |
@@ -39,8 +52,25 @@ until archives signed by retired IDs are no longer installed.
| Cloud | malformed, untrusted, invalid, or non-shared | any | reject before storage with `CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_INVALID` |
| OSS | absent legacy envelope | any | admitted to the dedicated profile |
| OSS | valid envelope declaring `shared-runtime-v1` | any | selected shared profile |
| OSS | malformed or invalid declaration | false | reject with `CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED` |
| OSS | malformed or invalid declaration | true | admitted to the dedicated profile |
| OSS | declaration signed by a **key this instance resolves** | false | reject with `CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED` |
| OSS | declaration signed by a **key this instance resolves** | true | admitted to the dedicated profile |
| OSS | declaration this instance **cannot resolve** (empty ring) | any | admitted to the dedicated profile |
The OSS row that matters for availability is the last one. Marketplace
packages are signed by the marketplace issuer and declare
`shared-runtime-v1`, while OSS ships an empty key ring by default. Treating that
as a rejection made every certified marketplace package uninstallable with
`CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED` before artifact storage. Because the
certificate is signed by an issuer the instance does not declare trusted, no
shared-runtime privilege may be granted, so admission degrades the install to
the existing `oss_dev` dedicated profile and records
`CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED`. This is not an escalation: it
withholds the shared profile rather than granting it.
A declaration is "resolvable" only when its `key_id` is present in the
configured ring. When the ring is configured and the declaration still fails
(malformed, `signature_invalid`, `digest_mismatch`, `unsupported_schema`, ...),
admission stays explicit and requires `administrator_force`.
`administrator_force` is deliberately strict: it is recognized only when the
install request carries boolean `true`. The local upload endpoint accepts the
+42 -2
View File
@@ -47,6 +47,7 @@ class AdmissionCode(str, Enum):
OSS_FORCE_REQUIRED = 'CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED'
OSS_FORCED_DEDICATED = 'CERTIFIED_PLUGIN_OSS_FORCED_DEDICATED'
OSS_CERTIFIED_DEDICATED = 'CERTIFIED_PLUGIN_OSS_CERTIFIED_DEDICATED'
OSS_UNTRUSTED_DEDICATED = 'CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED'
class PluginLogVisibility(str, Enum):
@@ -147,10 +148,19 @@ def verify_plugin_archive_certificate(
) -> VerifiedArchiveCertificate:
"""Use the SDK ZIP-comment API and retain its normalized-digest binding."""
verification = verify_archive(archive, trusted_public_key_ring(trusted_public_keys).get)
key_ring = trusted_public_key_ring(trusted_public_keys)
verification = verify_archive(archive, key_ring.get)
envelope = verification.envelope
runtime_profile = envelope.shared_runtime if envelope is not None else None
certificate_id = envelope.key_id if envelope is not None else None
# Record the issuer identity only when this instance actually resolved it
# through the configured ring. When the ring is empty (for example a
# self-hosted deployment that never configured
# ``plugin.certification.trusted_public_keys``) the declaration is
# unresolvable rather than rejected, so ``certificate_id`` stays unset and
# admission can degrade to the dedicated profile instead of blocking.
certificate_id = (
envelope.key_id if envelope is not None and envelope.key_id in key_ring else None
)
state = {
'absent': CertificateVerification.ABSENT,
'malformed': CertificateVerification.MALFORMED,
@@ -212,6 +222,23 @@ def decide_plugin_admission(
DEDICATED_RUNTIME,
)
# A self-hosted deployment that has not configured the issuer key ring cannot
# verify a marketplace archive's declaration. Falling back to the dedicated
# runtime (rather than blocking the install) is not a privilege escalation:
# the certificate is signed by an issuer this instance does not declare
# trusted, so no shared-runtime privilege may be granted. Shared-runtime
# isolation is therefore refused while the existing OSS dedicated profile
# keeps the install working. Cloud remains fail-closed above.
if not trusted_issuer_configured(facts):
return PluginAdmissionDecision(
AdmissionDisposition.DEDICATED_ALLOWED,
AdmissionCode.OSS_UNTRUSTED_DEDICATED,
DEDICATED_RUNTIME,
)
# The key ring is configured, yet the declaration still failed to verify
# (malformed, signature mismatch, unsupported schema, ...). Surface that as
# an explicit decision instead of silently degrading.
if administrator_force:
return PluginAdmissionDecision(
AdmissionDisposition.DEDICATED_ALLOWED,
@@ -226,6 +253,19 @@ def decide_plugin_admission(
)
def trusted_issuer_configured(facts: PluginCertificationFacts) -> bool:
"""Report whether the deployment holds a trusted issuer key for this archive.
``CertificateFacts`` records the verifying key only when it was resolved
through the configured ring, so an unresolvable declaration (``unknown_key``)
leaves ``certificate_id`` unset. That distinguishes "this operator never
configured the issuer" from "a configured trust decision rejected the
archive".
"""
return bool((facts.certificate.certificate_id or '').strip())
def decide_plugin_log_visibility(facts: PluginCertificationFacts) -> PluginLogVisibility:
"""Select the minimum log visibility compatible with a verified shared runtime."""
@@ -30,7 +30,7 @@ pytestmark = pytest.mark.integration
('cloud', 'signed_shared', False, 'shared-runtime-v1'),
('oss', 'signed_shared', False, 'shared-runtime-v1'),
('oss', 'legacy', False, 'dedicated'),
('oss', 'invalid_shared', True, 'dedicated'),
('oss', 'forged_shared', True, 'dedicated'),
],
)
async def test_install_plugin_admits_archive_before_persistence_and_applies_selected_profile(
@@ -81,7 +81,10 @@ async def test_cloud_rejects_untrusted_archive_before_storage_persistence_or_run
@pytest.mark.asyncio
async def test_oss_requires_explicit_administrator_force_for_declared_invalid_archive() -> None:
package, trusted_public_keys = _archive('invalid_shared')
# The archive declares the profile of a key this instance *does* trust but is
# signed by a different key, so the ring resolves its key_id and the failing
# signature is an explicit trust decision rather than an unconfigured ring.
package, trusted_public_keys = _archive('forged_shared')
connector, _execution_context, _binding = _connector('oss', trusted_public_keys)
with pytest.raises(ValueError, match='CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED'):
@@ -92,6 +95,51 @@ async def test_oss_requires_explicit_administrator_force_for_declared_invalid_ar
connector.handler.apply_plugin_installation.assert_not_awaited()
@pytest.mark.asyncio
async def test_oss_admits_unresolvable_declaration_on_the_dedicated_profile() -> None:
"""A self-hosted instance without the issuer key ring must still install.
Certified marketplace packages declare ``shared-runtime-v1`` and are signed by
the marketplace issuer. An OSS instance that never configured
``plugin.certification.trusted_public_keys`` cannot resolve that issuer, so it
must degrade the install to the dedicated profile instead of rejecting every
certified package with ``CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED``.
"""
package, _trusted_public_keys = _archive('signed_shared')
connector, execution_context, binding = _connector('oss', {})
await connector.install_plugin(PluginInstallSource.LOCAL, {'plugin_file': package})
persisted_info = connector._persist_installation_package.await_args.kwargs['install_info']
assert persisted_info['_certification']['runtime_profile'] == 'dedicated'
assert persisted_info['_certification']['admission_code'] == 'CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED'
assert persisted_info['_certification']['verification'] == 'invalid'
connector._store_artifact_package.assert_awaited_once_with(
execution_context,
hashlib.sha256(package).hexdigest(),
package,
)
connector.handler.apply_plugin_installation.assert_awaited_once_with(
binding,
artifact_package=package,
enabled=True,
)
@pytest.mark.asyncio
async def test_cloud_rejects_unresolvable_declaration_before_storage() -> None:
package, _trusted_public_keys = _archive('signed_shared')
connector, _execution_context, _binding = _connector('cloud', {})
with pytest.raises(ValueError, match='CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_INVALID'):
await connector.install_plugin(PluginInstallSource.LOCAL, {'plugin_file': package})
connector._store_artifact_package.assert_not_awaited()
connector._persist_installation_package.assert_not_awaited()
connector.handler.apply_plugin_installation.assert_not_awaited()
@pytest.mark.asyncio
@pytest.mark.parametrize('requested_version', [None, '1.0.0'])
@pytest.mark.parametrize('archive_kind', ['signed_shared', 'legacy'])
@@ -201,6 +249,24 @@ def _archive(kind: str) -> tuple[bytes, dict[str, str]]:
from langbot_plugin.certification import create_envelope, write_envelope
def _raw_public_key(private_key: Ed25519PrivateKey) -> str:
return base64.b64encode(
private_key.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
).decode('ascii')
if kind == 'forged_shared':
# Declares a key ID the instance trusts but signs with a different key,
# so the configured ring resolves the identity and rejects the signature.
trusted_key = Ed25519PrivateKey.generate()
forged_archive = write_envelope(
raw_archive,
create_envelope(raw_archive, 'trusted', Ed25519PrivateKey.generate().sign),
)
return forged_archive, {'trusted': _raw_public_key(trusted_key)}
signing_key = Ed25519PrivateKey.generate()
signed_archive = write_envelope(
raw_archive,
@@ -210,11 +276,7 @@ def _archive(kind: str) -> tuple[bytes, dict[str, str]]:
signing_key.sign,
),
)
trusted_key = signing_key.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
return signed_archive, {'ephemeral': base64.b64encode(trusted_key).decode('ascii')}
return signed_archive, {'ephemeral': _raw_public_key(signing_key)}
def _normalized_digest(archive: bytes) -> str:
@@ -8,27 +8,63 @@ import pytest
@pytest.mark.parametrize(
('deployment', 'certificate', 'force', 'expected_disposition', 'expected_code'),
('deployment', 'certificate', 'certificate_id', 'force', 'expected_disposition', 'expected_code'),
[
('cloud', ('valid', 'shared-runtime-v1'), False, 'shared_eligible', 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE'),
('cloud', ('absent', None), False, 'rejected', 'CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_REQUIRED'),
('cloud', ('malformed', None), False, 'rejected', 'CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_INVALID'),
('cloud', ('invalid', 'shared-runtime-v1'), True, 'rejected', 'CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_INVALID'),
('oss', ('absent', None), False, 'dedicated_allowed', 'CERTIFIED_PLUGIN_OSS_LEGACY_DEDICATED'),
('oss', ('valid', 'shared-runtime-v1'), False, 'shared_eligible', 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE'),
('cloud', ('valid', 'shared-runtime-v1'), 'issuer', False, 'shared_eligible', 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE'),
('cloud', ('absent', None), None, False, 'rejected', 'CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_REQUIRED'),
('cloud', ('malformed', None), None, False, 'rejected', 'CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_INVALID'),
(
'cloud',
('invalid', 'shared-runtime-v1'),
'issuer',
True,
'rejected',
'CERTIFIED_PLUGIN_CLOUD_CERTIFICATE_INVALID',
),
('oss', ('absent', None), None, False, 'dedicated_allowed', 'CERTIFIED_PLUGIN_OSS_LEGACY_DEDICATED'),
('oss', ('valid', 'shared-runtime-v1'), 'issuer', False, 'shared_eligible', 'CERTIFIED_PLUGIN_SHARED_ELIGIBLE'),
(
'oss',
('invalid', 'shared-runtime-v1'),
'issuer',
False,
'administrator_force_required',
'CERTIFIED_PLUGIN_OSS_FORCE_REQUIRED',
),
('oss', ('invalid', 'shared-runtime-v1'), True, 'dedicated_allowed', 'CERTIFIED_PLUGIN_OSS_FORCED_DEDICATED'),
(
'oss',
('invalid', 'shared-runtime-v1'),
'issuer',
True,
'dedicated_allowed',
'CERTIFIED_PLUGIN_OSS_FORCED_DEDICATED',
),
# A declaration the operator cannot resolve (no trusted key ring configured)
# must keep the OSS install working on the dedicated profile instead of
# blocking every certified marketplace package.
(
'oss',
('invalid', 'shared-runtime-v1'),
None,
False,
'dedicated_allowed',
'CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED',
),
(
'oss',
('invalid', 'shared-runtime-v1'),
None,
True,
'dedicated_allowed',
'CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED',
),
('oss', ('malformed', None), None, False, 'dedicated_allowed', 'CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED'),
],
)
def test_admission_policy_enforces_certification_matrix(
deployment: str,
certificate: tuple[str, str | None],
certificate_id: str | None,
force: bool,
expected_disposition: str,
expected_code: str,
@@ -47,6 +83,7 @@ def test_admission_policy_enforces_certification_matrix(
certificate=CertificateFacts(
verification=CertificateVerification(verification),
runtime_profile=runtime_profile,
certificate_id=certificate_id,
),
)