Skip to content

fix(updater): recognize published-port and network_mode Docker endpoints - #890

Merged
magrhino merged 8 commits into
mainfrom
claude/elated-leavitt-a00ded
Oct 16, 2026
Merged

magrhino merged 8 commits into
mainfrom
claude/elated-leavitt-a00ded

Conversation

@magrhino

@magrhino magrhino commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

Follow-up to #888. That PR blocks updates that would stop the container serving WUDup's DOCKER_HOST, but it only matched the host against container names, aliases, IPs and ID prefixes. So it failed open when the proxy is reached through a published host port or shares another container's network with network_mode. It could also falsely block a same-named service on a network WUDup is not attached to. This PR closes those gaps.

  • Published host port: a scoped container that publishes the DOCKER_HOST port (from NetworkSettings.Ports or HostConfig.PortBindings) is blocked. This covers a LAN IP, 127.0.0.1 and host.docker.internal. A tcp:// host with no port defaults to 2375, as the Docker CLI does. Bindings on wildcard or loopback addresses match. A binding on a different specific host address does not.
  • network_mode: service:/container:X: the guard follows HostConfig.NetworkMode to the container that owns the network and matches that container's addresses, names and ports. If the provider cannot be inspected, or the chain loops or runs longer than 5 hops, the update is blocked (fail closed).
  • Resolved address: when the DOCKER_HOST host resolves from WUDup, containers are matched by that IP instead of by name, so a same-named service on another network is no longer blocked. If the host does not resolve, name matching is used as before.
  • CONTAINER_DNS_FORMAT adds only long-standing inspect fields (NetworkSettings.Ports, HostConfig.PortBindings, HostConfig.NetworkMode), so the template stays valid on older engines.
  • Lookup only when needed: the DOCKER_HOST address is looked up only when a stack has a container to check, at most once per run, so a slow resolver no longer delays runs that check nothing. Tests stub the lookup so they never use the machine's real resolver.
  • Retag guard (fix(retag): block retags that would stop WUDup or its Docker endpoint #889): retag apply uses _docker_endpoint_services too, so it now gets the same port, network_mode and address matching, with the same lookup-only-when-needed behaviour.
  • Updated the limits paragraph in docs/wiki/webui-container.md. Remaining limits: host-network proxies and Docker contexts. A known trade-off: matching on the port alone also blocks an unrelated service that publishes the same host port.

Scope

Checklist

  • Tests added or updated
  • README.md or docs/ updated if install, deployment, config, or user-facing behavior changed
  • docs/examples/template.env or docs/examples/webui.env.example updated if environment variables or example config changed — N/A, no new variables
  • Release Please/changelog impact considered; no manual CHANGELOG.md edit needed outside release prep

Test plan

  • tests/run-all.sh — ran tests/run-all.sh --python instead (2224 passed, 1 skipped)
  • Focused test(s):
    • python -m pytest tests/test_python_container_identity_endpoint.py tests/test_python_update_from_wud_docker_endpoint.py tests/test_python_web_retag_apply_scope.py
    • tests/test-docker-update-from-wud.sh
    • ruff check .
  • Docker or Compose validation, if container behavior changed:
    • Ran CONTAINER_DNS_FORMAT with docker inspect on Docker 29.8.2 (API 1.56): a container publishing 23750->2375/tcp and a second container on --network container:<first>. Both returned valid JSON with the expected Ports, PortBindings and NetworkMode: container:<full id>. Service is null without a Compose label, which the guard handles.
    • Ran _docker_endpoint_services on that real output: tcp://127.0.0.1:23750 and tcp://host.docker.internal:23750 blocked both containers, tcp://127.0.0.1:23751 blocked neither, and tcp://wudup-tpl-check:2375 blocked both through the name fallback.
    • tests/e2e-docker-compose.sh fails locally on OrbStack/macOS in the stack-recreate scenario ("Could not verify whether selected services are running"). It fails the same way on unmodified main, uses a unix-socket DOCKER_HOST (so this guard does not run), and passes in CI's docker e2e job.

Danger: docker-review-complete. The only Docker-adjacent code change is the CONTAINER_DNS_FORMAT inspect template in src/wudup/docker_cli.py. It adds only long-standing inspect fields and was run on a real engine (Docker 29.8.2, API 1.56), as described in the test plan above. CI's docker build and docker e2e jobs pass.

Danger: docker-socket-review-complete. The socket-proxy assumptions were reviewed by two read-only high-risk reviews and checked against real engine output (see above). The guard only adds blocks before mutation: every lookup failure blocks the update, and unix-socket DOCKER_HOST values skip the check. Read-only and mutation defaults are unchanged.

@sourcery-ai

sourcery-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Sorry @magrhino, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 15 hours and 59 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6079bda7-6c45-4d0a-8e6f-f1b9c3edcdb2
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR hardens the Docker endpoint recreate guard by modeling DOCKER_HOST as a resolved host/port endpoint, matching published TCP ports and actual container IPs, following network_mode container providers with bounded fail-closed traversal, and documenting and testing the resulting behavior.

File-Level Changes

Change Details Files
Expanded Docker endpoint identity matching beyond container DNS names to include resolved IPs and published host ports, while preserving scoped network/address behavior.
  • Parse network DOCKER_HOST values into host, port, and locally resolved addresses, including the tcp default port 2375.
  • Match container network addresses when resolution succeeds, fall back to names when it does not, and recognize TCP bindings from inspect port metadata.
  • Apply host-binding rules for wildcard, loopback, matching, and unrelated specific host addresses.
src/wudup/container_identity.py
src/wudup/docker_cli.py
tests/test_python_update_from_wud_docker_endpoint.py
Added fail-closed traversal for containers sharing network namespaces through network_mode.
  • Follow container: network providers before endpoint matching with a five-hop limit.
  • Reuse inspected provider details for names, addresses, and published ports.
  • Block updates when provider inspection fails or the network-mode chain loops/exceeds the limit.
src/wudup/updater_preflight.py
tests/test_python_update_from_wud_docker_endpoint.py
Updated user-facing documentation to describe the expanded endpoint protection and remaining limitations.
  • Document published-port, resolved-address, and shared-network detection behavior.
  • Call out same-port false positives and unsupported host-network and Docker-context setups.
docs/wiki/webui-container.md
Added focused coverage for endpoint parsing, address resolution, port matching, network-mode providers, fail-closed behavior, and false-positive avoidance.
  • Test TCP port defaults, IPv4/IPv6 normalization, binding address rules, and UDP exclusion.
  • Test shared-network provider matching, lookup failures, cycles, and same-named containers on unrelated networks.
tests/test_python_update_from_wud_docker_endpoint.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

The Docker endpoint guard now also blocks a recreate scope whose container publishes the DOCKER_HOST port on the Docker host (LAN IP, 127.0.0.1, host.docker.internal), and checks a container that shares another container's network through that provider. When the DOCKER_HOST host resolves, containers are matched by that address instead of by name, so a same-named service on a network WUDup is not attached to is no longer blocked. Provider lookups and network_mode cycles fail closed.
Web test fixtures set DOCKER_HOST=tcp://docker:2375, so the endpoint guard queried the machine's real resolver for "docker". An autouse fixture now returns no addresses, keeping results and timing independent of the network.
Split published-port parsing into small helpers to bring its cognitive complexity under the limit, and drop UnicodeError from the resolver's handler since ValueError already covers it. No behavior change.
…network blocking

Look up the DOCKER_HOST address only when a stack has a container to check, and at most once per run, so a slow resolver no longer delays runs that check nothing. The retag recreate-scope guard shares the endpoint matching and resolves the same way. Replace the narrowing assert with an explicit ValueError that the guard already treats as a failed lookup. Document that services sharing the endpoint's network are blocked even when their restart would leave the proxy running, and cover IPv6-only addresses, the network_mode hop limit, configured-only port bindings, and retag published-port and network_mode matching.
@magrhino
magrhino force-pushed the claude/elated-leavitt-a00ded branch from 24b70f2 to f5f2c30 Compare October 16, 2026 22:03
@magrhino
magrhino changed the base branch from claude/recursing-taussig-50dde9 to main October 16, 2026 22:03
Construct DockerEndpoint directly instead of through dataclasses.replace, so static analysis sees the declared return type. No behavior change.
Move the container_identity parsing and matching unit tests out of the updater end-to-end test file, which had grown past the maintainability warning threshold. No tests were added or removed.
Build settings and stack selections before the raises block so each exception test has a single call that can raise.
sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Oct 16, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sourcery assessment

Approved.

@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/wudup/container_identity.py 93.81% 2 Missing and 4 partials ⚠️
src/wudup/updater_preflight.py 89.65% 1 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Exercise the retag guard's ValueError (network_mode cycle) and TypeError (non-object inspect output) paths, which previously had retag coverage only for CommandError. Document that ssh:// DOCKER_HOST values skip the Docker endpoint check, as unix sockets do.
@sonarqubecloud

Copy link
Copy Markdown

@magrhino
magrhino merged commit 734fee8 into main Oct 16, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant