Skip to content

security: extension capabilities are declared but never enforced #397

Description

@codeforester

Problem

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_version is 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:

  1. 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.
  2. Demote them. Document capabilities as informational metadata only — useful for inventory and
    for 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(): 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or product improvementsecuritySecurity hardening or vulnerability work

Type

No type

Projects

  • Status
    Backlog

Relationships

None yet

Development

No branches or pull requests

Issue actions