You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ExtensionDescriptor.capabilities is parsed from entry-point extras and exposed publicly, but
nothing in the package ever reads it to make a decision. It looks like a permission model and grants
nothing.
_descriptor_from_entry_point() collects base-cli-cap-* extras into a sorted tuple
(lib/python/base_cli/extensions.py:364-370), and docs/extensions.md:41-51 tells authors to
"Declare the SDK version and capabilities in the entry-point extras". api_versionis enforced — ExtensionCompatibilityError is raised in load() when it is unsupported (line 236-237) — which
makes the neighbouring unenforced field more misleading, not less: an adopter reasonably infers that
declaring base-cli-cap-network means something.
A secondary weakness in the same area: _allowed() (lib/python/base_cli/extensions.py:312-319)
admits a descriptor if the allowlist contains the bare name, the group:name key, or the
distribution name. Allowlisting a bare name therefore admits that name from any group and any
distribution, and allowlisting a distribution admits every entry point it ships — including ones
added in a later release of that distribution. For an allowlist, which exists to be a trust
boundary, the loosest match winning is the wrong default.
Verified evidence
Reviewed 2026-09-30 at a58ec109349fa3f3d03eae5b0de078b39ea361a2.
$ grep -rn "capabilities" lib/python/base_cli/ docs/extensions.md
extensions.py:109: capabilities: tuple[str, ...] = ()
extensions.py:364: capabilities = tuple(
extensions.py:379: capabilities=capabilities,
docs/extensions.md:41:Declare the SDK version and capabilities in the entry-point extras. The
docs/extensions.md:51:`ExtensionDescriptor.api_version` and `.capabilities` expose this metadata.
Three references in the package, all construction or storage; no comparison, no filter, no gate.
Proposal
Pick one and make it explicit:
Enforce them. Give ExtensionDiscovery a supported_capabilities / required_capabilities
parameter, refuse to load an extension declaring a capability the host has not granted, and raise a
typed error naming the capability — mirroring how api_version already works. Note honestly in the
docs that this is declarative: a loaded Python extension runs in-process with full privileges, so
the gate expresses intent and enables audit, it does not sandbox.
Separately, tighten _allowed(): make group:name the canonical allowlist form, treat a bare name as
matching only within an explicitly requested group, and require distribution entries to be written as
something unambiguous such as dist:<name>. Document the precedence.
Acceptance criteria
capabilities is either enforced with a typed refusal and tests, or documented as
non-enforcing metadata in docs/extensions.md.
If enforced, the documentation states clearly that it is not a sandbox.
Allowlist matching semantics are documented, and a test asserts a bare-name entry does not admit an
entry point from an unintended group or distribution.
docs/security-threat-model.md reflects whatever the extension trust boundary actually is.
Non-goals
Do not build extension sandboxing or subprocess isolation here.
Do not change EXTENSION_API_VERSION or the entry-point group names.
Problem
ExtensionDescriptor.capabilitiesis parsed from entry-point extras and exposed publicly, butnothing in the package ever reads it to make a decision. It looks like a permission model and grants
nothing.
_descriptor_from_entry_point()collectsbase-cli-cap-*extras into a sorted tuple(
lib/python/base_cli/extensions.py:364-370), anddocs/extensions.md:41-51tells authors to"Declare the SDK version and capabilities in the entry-point extras".
api_versionis enforced —ExtensionCompatibilityErroris raised inload()when it is unsupported (line 236-237) — whichmakes the neighbouring unenforced field more misleading, not less: an adopter reasonably infers that
declaring
base-cli-cap-networkmeans something.A secondary weakness in the same area:
_allowed()(lib/python/base_cli/extensions.py:312-319)admits a descriptor if the allowlist contains the bare name, the
group:namekey, or thedistribution name. Allowlisting a bare name therefore admits that name from any group and any
distribution, and allowlisting a distribution admits every entry point it ships — including ones
added in a later release of that distribution. For an allowlist, which exists to be a trust
boundary, the loosest match winning is the wrong default.
Verified evidence
Reviewed 2026-09-30 at
a58ec109349fa3f3d03eae5b0de078b39ea361a2.Three references in the package, all construction or storage; no comparison, no filter, no gate.
Proposal
Pick one and make it explicit:
ExtensionDiscoveryasupported_capabilities/required_capabilitiesparameter, refuse to load an extension declaring a capability the host has not granted, and raise a
typed error naming the capability — mirroring how
api_versionalready works. Note honestly in thedocs that this is declarative: a loaded Python extension runs in-process with full privileges, so
the gate expresses intent and enables audit, it does not sandbox.
capabilitiesas informational metadata only — useful for inventory andfor the CLI catalog work in [platform] Define signed CLI Catalog ingestion and governance boundaries #277 — and say plainly that it grants and restricts nothing.
Separately, tighten
_allowed(): makegroup:namethe canonical allowlist form, treat a bare name asmatching only within an explicitly requested group, and require distribution entries to be written as
something unambiguous such as
dist:<name>. Document the precedence.Acceptance criteria
capabilitiesis either enforced with a typed refusal and tests, or documented asnon-enforcing metadata in
docs/extensions.md.entry point from an unintended group or distribution.
docs/security-threat-model.mdreflects whatever the extension trust boundary actually is.Non-goals
EXTENSION_API_VERSIONor the entry-point group names.