Skip to content

fix: prevent current-directory Docker executable discovery - #5173

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

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

Conversation

@mnriem

@mnriem mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Description

Prevent automatic Docker executable discovery from selecting binaries in the current working directory or its descendants, including symlink targets.

Search qualified executable paths in absolute PATH entries, preserving Windows PATHEXT handling without Python 3.11's implicit current-directory search. Keep the detected absolute executable path for the Docker Agent version probe and runtime invocation. Explicit operator overrides remain supported.

Add regression coverage for current-directory lookup, relative and empty search entries, symlinks, installed executables, and probe failures. No documentation changes.

Testing

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

Used this worktree's own virtual environment for pytest, as documented in CONTRIBUTING.md:

  • uv sync --extra test — passed.
  • uv run specify --help — passed.
  • .venv/bin/python -m pytest tests/test_utils.py tests/specify_cli/test_check_tool.py tests/integrations/test_integration_docker_agent.py tests/test_workflows.py -q --tb=short — 916 passed.
  • LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q — 13,680 passed, 130 skipped, 63 warnings.

Executed on macOS with Python 3.13. Windows lookup behavior is modeled in regression tests; native Windows execution was not run. Sample-project scaffolding is not applicable to this executable-discovery fix.

AI Disclosure

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

AI disclosure: Authored on behalf of @mnriem by GitHub Copilot App using GPT-6.1 Sol (model ID: gpt-6.1-sol), autonomous agent mode; the reasoning-effort setting is not exposed in this session. AI assistance covered assessment, implementation, regression tests, automated test execution, and this PR description.

Resolve Docker executables through qualified absolute PATH entries, skip working-directory targets and pin detected runtime paths. Preserve Windows PATHEXT lookup without implicit current-directory search.

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:27

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

Dispatch can still resolve and execute a checkout-local docker-agent rejected by automatic discovery, bypassing the intended safety check.

3 open issues
What changed in this PR

This PR hardens Specify CLI’s automatic Docker Agent discovery against executables in the current working directory or its descendants.

Changes:

  • Search absolute PATH entries outside the working-directory tree, checking resolved symlink targets and preserving Windows extension handling.
  • Reuse detected absolute paths for version probes and runtime arguments while retaining explicit overrides.
  • Add discovery regression tests and update existing mocks and command expectations.

Review validation used an isolated dispatch check; the full suite and native Windows execution were not run.

AI-generated review for the requester by GitHub Copilot (model/settings unavailable; automated mode; analysis and feedback only).

File Description
tests/​test_utils.py Adds discovery regressions for PATH filtering, symlinks, Windows lookup, and probe failures.
tests/​specify_cli/​test_check_tool.py Updates Docker Agent availability mocks to use the new resolver.
tests/​integrations/​test_integration_docker_agent.py Updates resolver mocks and expects absolute executable paths.
src/​specify_cli/​_utils.py Adds restricted executable discovery and preserves detected paths.

Review effort: Max (auto)

Comment thread src/specify_cli/_utils.py Outdated
Comment on lines +60 to +63
standalone = _find_docker_executable("docker-agent")
if standalone is not None:
return [standalone, "run"]
executable = _find_docker_executable("docker")
Comment thread tests/test_utils.py
Comment thread tests/test_utils.py Outdated
@mnriem mnriem added the triage-must-have Verdict: high-value, important work for Spec Kit — do first label Oct 29, 2026
Reject dispatch when automatic discovery or the plugin probe fails while preserving explicit executable overrides. Pin Windows fixture suffixes and isolate symlink-dependent tests.

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:26
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed dd92e86. Docker Agent dispatch now fails explicitly when automatic discovery or the plugin probe fails, without restoring an unrestricted executable lookup; explicit executable overrides remain supported. Windows fixture suffixes are pinned through PATHEXT, and symlinks are created only where required, with unsupported cases skipped. Automated validation: LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q -n 4 passed with 13,684 passed and 130 skipped; the focused discovery, integration, and workflow suite passed all 920 tests. With symlink creation disabled, the utility tests reported 16 passed and only the four symlink cases skipped. Native Windows execution was not run locally. This summary was authored on behalf of @mnriem by GitHub Copilot App using GPT-6.1 Sol (gpt-6.1-sol), autonomous agent mode; the reasoning-effort setting is not exposed. AI assistance covered implementation, regression tests, automated validation, and this comment.

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

Executable discovery can still run checkout-local Docker binaries, breaks Windows workflow tests, and allows missing-executable errors to bypass configured workflow error handling.

4 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Preserve standard executable extensions when PATHEXT is empty

src/​specify_cli/​_utils.py:31

With PATHEXT="", this creates no filenames, so installed docker-agent.exe and docker.exe are reported as unavailable. Preserve the standard-extension fallback used by shutil.which() for both unset and empty values, and add an empty-PATHEXT regression.

AI-generated review by GitHub Copilot for the requesting reviewer; reviewer identity and model/settings unavailable; autonomous analysis and comment editing.

Medium severity Reject case-insensitive checkout-local Docker binaries

src/​specify_cli/​_utils.py:47

On case-insensitive macOS filesystems, a differently cased absolute PATH entry can still select a checkout-local Docker binary. resolve() preserves the spelling, so /Users/me/PROJECT/bin is not lexically under CWD /Users/me/project, and both containment guards allow it. Check the resolved executable's ancestors by filesystem identity before returning it, and add a mixed-case-path regression.

AI-generated review by GitHub Copilot for the requesting reviewer; reviewer identity and model/settings unavailable; autonomous analysis and comment editing.

2 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/_utils.py Outdated

def _find_docker_executable(name: str) -> str | None:
"""Search absolute PATH entries outside the CWD subtree for a Docker CLI."""
cwd = Path.cwd().resolve()
Comment thread src/specify_cli/_utils.py Outdated
continue
# A qualified name bypasses Python 3.11's implicit Windows CWD search.
for filename in names:
resolved = shutil.which(str(directory / filename))
Comment thread src/specify_cli/integrations/docker_agent/__init__.py Outdated
Exclude the effective runtime project and case-insensitive directory aliases from automatic discovery. Restore empty-PATHEXT fallback and make Windows workflow fixtures independent of native executable lookup. Report prompt discovery failures as failed steps while preserving fatal adapter-integrity errors.

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 14:28
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed this round in commit 2843fcb. Automatic Docker discovery excludes both the launch directory and the effective runtime project, including case-insensitive filesystem aliases and resolved symlink targets. Empty PATHEXT uses the standard extension fallback, and Docker workflow fixtures mock the restricted resolver independently of later executable lookups. Prompt discovery errors now return failed step results and honor continue_on_error, while adapter-integrity failures retain their fatal workflow-level handling. Automated validation on macOS: LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q -n 4 passed with 13,702 passed and 130 skipped; the focused discovery, integration, and workflow suite passed all 938 tests; all 12 installed-adapter dispatch-context cases passed; uvx ruff@0.15.0 check src tests passed. Native Windows execution was not run locally. This summary was authored on behalf of @mnriem by GitHub Copilot App using GPT-6.1 Sol (gpt-6.1-sol), autonomous agent mode; the reasoning-effort setting is not exposed. AI assistance covered implementation, regression tests, automated validation, and this comment.

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

Docker discovery can abort on inaccessible or cyclic PATH entries, its new restrictions remain undocumented, and the shared prompt-handler change requires a separately justified prerequisite PR.

7 open issues

Review effort: Max (auto)

Comment thread src/specify_cli/_utils.py Outdated
Comment thread src/specify_cli/workflows/step/prompt/__init__.py Outdated
Comment thread src/specify_cli/integrations/docker_agent/__init__.py Outdated
…andling

Warn and continue past inaccessible or cyclic PATH candidates. Keep unavailable-executable handling inside the Docker integration through the optional argv contract, restoring the shared prompt handler to its original behavior.

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 16:19
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed d21cf2f. Inaccessible or cyclic Docker search candidates now produce a warning and are skipped so discovery can reach a later valid installation. Following @mnriem's scope decision, unavailable executable handling stays inside the Docker integration through its existing optional-argv contract; the shared prompt handler is restored exactly to its original state, so no prerequisite PR is needed. The existing failed-dispatch handling continues to respect continue_on_error, and adapter-integrity behavior is unchanged. Documentation remains unchanged at @mnriem's explicit direction. Automated validation on macOS: LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q -n 4 passed with 13,734 passed and 130 skipped; all 970 focused tests and all 12 installed-adapter dispatch-context cases passed; uvx ruff@0.15.0 check src tests passed. Native Windows execution was not run locally. This summary was authored on behalf of @mnriem by GitHub Copilot App using GPT-6.1 Sol (gpt-6.1-sol), autonomous agent mode; the reasoning-effort setting is not exposed. AI assistance covered implementation, regression tests, automated validation, and this comment.

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.

Honor a non-empty executable override even when it equals the integration key. Keep unset, empty and whitespace-only values on restricted automatic discovery, with build and dispatch regressions.

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 16:49
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 7cb9c24. This review publishes no new inline finding and does not describe the omitted override concern, so I cannot confirm that the following fix addresses that exact concern. Independently, regression tests reproduced a concrete override bug: an explicit SPECKIT_INTEGRATION_DOCKER_AGENT_EXECUTABLE=docker-agent was treated as an unset override. Non-empty explicit overrides now retain operator intent even when they match the default integration key; unset, empty, and whitespace-only values still use restricted automatic discovery. Build-argument and dispatch regressions cover this distinction without relaxing automatic-discovery restrictions. Automated validation on macOS: LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -q -n 4 passed with 13,740 passed and 130 skipped; all 976 focused tests passed; uvx ruff@0.15.0 check src tests passed. Native Windows execution was not run locally. This summary was authored on behalf of @mnriem by GitHub Copilot App using GPT-6.1 Sol (gpt-6.1-sol), autonomous agent mode; the reasoning-effort setting is not exposed. AI assistance covered assessment, implementation, regression tests, automated validation, and this comment.

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

The Windows bare override test can reject a valid relative path returned by shutil.which because it compares absolute path strings instead of file identity.

4 open issues

Review effort: Max (auto)

)

assert result == {"exit_code": 0, "stdout": "done", "stderr": ""}
assert run.call_args.args[0][0] == str(executable)

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