Skip to content

bundle catalog add rewrites Windows drive paths as POSIX paths #5204

Open

Description

@soarkit

Summary

_is_local_path() explicitly recognizes a Windows drive path such as
C:\catalog.json, but _canonicalize_url() then passes it to the host-native
Path(...).resolve(). On Linux and macOS that turns the Windows absolute path
into a path relative to the current project, and add_source() persists the
rewritten value into the catalog configuration.

Environment

  • Repository: github/spec-kit
  • Target revision: 1c799bda6c136fcf42fb3475a922ccaac57e6c71
  • Platform: macOS (POSIX), Python 3.13
  • File: src/specify_cli/bundles/catalog_config.py:96-116
    (_is_local_path, _canonicalize_url), used by add_source() at :184

Expected behavior

A Windows absolute path is either preserved verbatim in the configuration or
rejected as non-local on a POSIX host. It must not be silently rewritten into a
different path.

Actual behavior

Adding C:\catalog.json on macOS persists:

catalogs:
- id: c---catalog
  url: /private/tmp/.../project/C:\catalog.json
  priority: 50
  install_policy: install-allowed

The stored URL points at a non-existent file whose name contains a backslash
and a colon; later catalog reads fail to resolve the intended source.

Root cause

_WINDOWS_DRIVE_RE = re.compile(r"^[A-Za-z]:[\\/]")

def _is_local_path(url: str) -> bool:
    if _WINDOWS_DRIVE_RE.match(url):
        return True
    ...

def _canonicalize_url(url: str) -> str:
    if _is_local_path(url):
        return str(Path(url).expanduser().resolve())
    return url

The Windows-drive branch is platform-independent, while Path is host-native.
On POSIX, Path(r"C:\catalog.json").resolve() produces
<cwd>/C:\catalog.json; on Windows it would be absolute. The same function is
reached from the public bundle catalog add path through add_source().

Impact

  • A catalog configuration produced on Windows and consumed on Linux/macOS (or a
    Windows path passed on a POSIX host) is silently corrupted instead of being
    rejected.
  • The stored catalog id (c---catalog) and URL both diverge from the user's
    input, and the source cannot be loaded.

Suggested fix

Preserve Windows drive paths on non-Windows hosts instead of resolving them
with the host-native Path:

def _canonicalize_url(url: str) -> str:
    if _WINDOWS_DRIVE_RE.match(url):
        return url            # do not reinterpret on POSIX
    if _is_local_path(url):
        return str(Path(url).expanduser().resolve())
    return url

or canonicalize with PureWindowsPath when the host is POSIX and the input
matches the Windows-drive form, and reject the value if it cannot be resolved.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions