Skip to content

fix(extensions): env-var config leaks across prefix-colliding extension IDs in _get_env_config #3494

Description

@thejesh23

Summary

ConfigManager._get_env_config reads SPECKIT_<EXT_ID>_... environment variables into the current extension's config by prefix-matching on SPECKIT_<EXT_ID>_. But because the underscore is also used as the delimiter between the extension ID and the config path, a longer extension ID whose canonical prefix begins with a shorter extension ID's prefix will leak into the shorter one.

Reproduction

Two extensions co-installed, git and git-hooks (both plausible names — git is a bundled extension in this repo):

import os, tempfile
from pathlib import Path
from specify_cli.extensions import ConfigManager

os.environ["SPECKIT_GIT_URL"]        = "value_for_git_ext"
os.environ["SPECKIT_GIT_HOOKS_URL"]  = "value_for_git-hooks_ext"

with tempfile.TemporaryDirectory() as td:
    cm = ConfigManager(Path(td), "git")
    print(cm._get_env_config())

Output (against main, 0.12.13.dev0):

{'url': 'value_for_git_ext', 'hooks': {'url': 'value_for_git-hooks_ext'}}

The git extension has picked up git-hooks's URL variable as hooks.url.

Root cause

src/specify_cli/extensions/__init__.py:2757-2760:

ext_id_upper = self.extension_id.replace("-", "_").upper()
prefix       = f"SPECKIT_{ext_id_upper}_"

for key, value in os.environ.items():
    if not key.startswith(prefix):
        continue
    # ...

SPECKIT_GIT_HOOKS_URL starts with SPECKIT_GIT_, so it is accepted as belonging to the git extension. The remainder HOOKS_URL is then interpreted as a nested key path hooks.url.

Impact:

  • Config leakage: config intended for one extension surfaces inside another extension's config namespace (extension.get_config()).
  • Behavior corruption via hooks: _evaluate_condition("config.hooks.url is set", ...) becomes True for the wrong extension, potentially firing hooks that would not otherwise fire.
  • Precedence violation: env vars are documented to override YAML config for the extension they target; here they silently override an unrelated extension's YAML.

This is not the same case as #3350 (which fixed intra-extension prefix collisions between two keys of the same extension). This report is about cross-extension leak between different extensions whose IDs share a prefix.

Suggested fix

Skip env vars whose remainder-after-prefix begins with another installed extension's normalized ID + _. This requires reading the installed extensions list (or the registry) at _get_env_config time; a small refactor.

I have a fix + regression tests ready and can open a PR.


Disclosure: this issue and the accompanying PR were prepared with Claude (Anthropic) assistance. Reproducer executed against a fresh clone of main.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions