Skip to content

fix: isolate self-upgrade installer lookup from project paths - #5175

Open
mnriem wants to merge 5 commits into
github:mainfrom
mnriem:mnriem-installer-lookup-security
Open

mnriem wants to merge 5 commits into
github:mainfrom
mnriem:mnriem-installer-lookup-security

Conversation

@mnriem

@mnriem mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Description

Resolve uv and pipx installers from absolute PATH directories outside the current working directory and Git worktree. Use the same lookup for registry detection and self-upgrades, bypassing Windows' implicit current-directory search while preserving eligible PATH ordering and PATHEXT handling.

Add regression coverage for unsafe search paths, symlinked directories, absolute installer execution, missing-installer errors, and normal user-local installations.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

The full suite used the documented worktree-local interpreter instead of the generic command above:

  • uv sync --extra test --quiet — passed.
  • LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q — passed: 13,702 passed, 130 skipped.
  • LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/test_version_*.py tests/specify_cli/selfs tests/specify_cli/test_root_version_callback.py -q --tb=short — passed: 212 passed, 1 skipped.
  • uv run specify --help — passed.

Run on macOS; native Windows validation was not performed. Sample-project testing is not applicable because this change does not affect scaffolding or generated project assets.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: GitHub Copilot using GPT-6.1 Sol in autonomous mode generated the implementation, tests, commit message, and pull request body, and ran validation. The initial alert assessment used a security-review subagent whose model was not reported by the runtime. No documentation changes are included.

Resolve uv and pipx through absolute PATH directories outside the working directory and Git worktree for registry probes and self-upgrades. Preserve Windows PATHEXT handling and eligible PATH ordering, with regression coverage for unsafe search paths and normal installations.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 29, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical symlink bypasses in executable lookup allow registry detection and self-upgrade to execute project-controlled code despite worktree exclusions.

2 open issues
What changed in this PR

This PR centralizes installer lookup for Specify CLI’s registry detection and self-upgrade workflow to reduce execution of project-controlled uv and pipx binaries.

Changes:

  • Filters installer searches to eligible absolute PATH directories, preserving ordering and Windows PATHEXT handling.
  • Shares lookup between registry probes and upgrade commands.
  • Adds regression coverage for unsafe paths, symlinks, absolute execution, and missing installers.

AI-assisted review for the requester: GitHub Copilot; model unknown; autonomous analysis, suggested fixes, and summary.

File Description
tests/​specify_cli/​test_version_execution.py Updates execution-test mocking to use the shared installer lookup.
tests/​specify_cli/​test_version_detection.py Adds lookup security regressions and adapts existing mocks.
src/​specify_cli/​_version.py Adds filtered installer discovery and uses it for detection and upgrades.

Review effort: Max (auto)

Comment thread src/specify_cli/_version.py Outdated
Comment thread src/specify_cli/_version.py Outdated
@mnriem mnriem added the triage-must-have Verdict: high-value, important work for Spec Kit — do first label Oct 29, 2026
Validate directory ancestors and intermediate symlink targets, and reject installer executables resolving into the working directory or Git worktree. Execute the validated absolute target and use absolute installer fixtures in lookup mocks. Add regression coverage for alias, file, and combined symlink bypasses across discovery and upgrades.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 29, 2026 13:38
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed both findings in ee20e70: installer lookup now checks directory ancestors and intermediate symlink targets, rejects executables resolving into the working directory or Git worktree, and executes the validated absolute target. Lookup mocks now return absolute installer paths, with regression coverage for alias, file, and combined symlink bypasses across lookup, registry detection, and upgrades. LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q -n 2 passed: 13,756 passed, 130 skipped. Validation ran on macOS; native Windows validation was not performed. Posted on behalf of @mnriem by GitHub Copilot (GPT-6.1 Sol, autonomous mode), which authored the implementation, tests, and this summary and ran validation.

Reuse the existing comparison-only extended-path normalization for installer cwd and worktree checks. Preserve original filesystem access paths while recognizing equivalent Windows drive and UNC namespace spellings, including intermediate symlink targets. Add portable containment regressions for plain and extended roots and targets.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical symlink, junction, and platform-specific path-comparison bypasses still allow project- or CWD-controlled paths to influence installer selection.

6 open issues
2 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/_version.py Outdated
Comment thread src/specify_cli/_version.py Outdated
Comment thread src/specify_cli/_version.py Outdated
Comment thread src/specify_cli/_version.py Outdated
Comment thread src/specify_cli/_version.py Outdated
Comment thread src/specify_cli/_version.py Outdated
Copilot AI auto review requested due to automatic review settings October 29, 2026 14:03
Inspect intermediate executable links even without a Git root, compare filesystem identities for cwd and worktree boundaries, and reject unsupported Windows reparse points. Warn and continue after per-entry inspection failures while preserving the selected absolute installer shim spelling. Add case-alias, CWD-chain, dispatcher, failure-recovery, and native Windows namespace/junction regressions.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed this review in cd1e7d2 and b98bc93. Installer lookup now inspects intermediate executable links even without a Git root, uses filesystem identities for CWD and worktree boundaries, recognizes equivalent Windows namespace spellings, rejects unsupported reparse points including junctions, warns and continues after per-entry inspection failures, and executes the selected absolute shim spelling rather than its dispatcher target. Added regressions for CWD link chains, case-insensitive aliases, inspection failures, differently named dispatchers, and native Windows namespace/junction paths. The focused local tests passed: 489 passed, 66 skipped. CI has passed on Linux and macOS with Python 3.13/3.14 and Windows with Python 3.14; Windows Python 3.13 is still running. Posted on behalf of @mnriem by GitHub Copilot (GPT-6.1 Sol, autonomous mode), which authored the implementation, tests, and this summary and ran validation.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Installer validation still permits working-directory-controlled symlink chains and project-controlled Windows junctions, while returning resolved targets breaks symlink-based dispatch.

No open issues 🎉

3 previously missed issues

In code that hasn't changed since last review

High severity Detect Windows junctions in installer symlink validation

src/​specify_cli/​_version.py:502

Windows junctions are not detected by is_symlink(), so a chain such as C:\alias -> C:\project\bin -> C:\external passes these checks despite traversing a project-controlled junction. Registry probes and upgrades can therefore use an installer selected through the worktree. Inspect junction targets or reject non-symlink reparse points as src/specify_cli/file_helper.py:79-88 does, and add a native Windows regression.

High severity Validate intermediate symlinks without a Git ancestor

src/​specify_cli/​_version.py:508

Without a Git ancestor, this early return accepts an external bin/uv -> $PWD/uv -> /outside/uv chain because only the endpoint parents are checked. The working directory therefore still controls installer selection. Traverse intermediate symlinks even without a worktree and reject links located directly in cwd, while preserving allowed user-local descendant directories; add regressions for both installers.

Medium severity Return the validated entrypoint for symlink dispatch

src/​specify_cli/​_version.py:545

Returning the resolved target changes the executable name for symlink-based dispatchers, so a valid bin/uv -> dispatcher runs in the wrong mode and breaks registry detection and self-upgrade. Return the validated absolute entrypoint instead, and update the external-file-symlink test to check dispatch behavior rather than require the target path.

6 resolved since last review

Review effort: Max (auto)

Copilot AI auto review requested due to automatic review settings October 29, 2026 14:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Installer resolution still allows CWD-controlled directory-symlink chains to override trusted installers when no Git ancestor exists, while blanket junction rejection also blocks legitimate Windows installations.

1 open issue

Review effort: Max (auto)

Comment on lines +507 to +513
is_link = stat.S_ISLNK(entry_status.st_mode)
if (
getattr(entry_status, "st_file_attributes", 0)
& stat.FILE_ATTRIBUTE_REPARSE_POINT
and not is_link
):
return False
Recognize directory mount-point reparse tags and inspect their targets instead of rejecting every Windows junction. Keep unsupported reparse types and CWD-controlled intermediate directory links excluded. Add portable and native Windows coverage for safe external installer paths, registry detection, upgrades, and unsafe junction chains.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 29, 2026 15:52

This branch has not been deployed

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

Labels

triage-must-have Verdict: high-value, important work for Spec Kit — do first

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants