#13798: fix(macos): prevent PortGuard from killing Docker Desktop in remote mode
app: macos
size: S
Cluster:
Docker and Deployment Improvements
## Summary
- Problem: PortGuardian.sweep() kills Docker Desktop when the gateway runs in a container in remote mode
- Why it matters: In remote mode, the gateway port is externally managed (Docker port-forward, SSH tunnel, direct WS, etc.), so any process holding it should be accepted — not terminated
- What changed: Added defense-in-depth guard in `sweep()` that skips kill in remote mode; refactored `isExpected()` to accept any process on gateway port in remote mode
- What did NOT change (scope boundary): Local mode behavior is fully preserved — unexpected processes on the gateway port are still terminated as before
## Change Type (select all)
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [x] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [ ] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #6755
## User-visible / Behavior Changes
In remote mode, Docker Desktop (and other port-forwarding processes like Podman, SSH) are no longer killed by PortGuardian. Local mode behavior is unchanged.
## Security Impact (required)
- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `No`
- Command/tool execution surface changed? `No`
- Data access scope changed? `No`
## Repro + Verification
### Environment
- OS: macOS
- Runtime/container: Docker Desktop (remote mode gateway)
### Steps
1. Run the gateway inside a Docker container in remote mode
2. Docker Desktop holds the gateway port via port-forwarding
3. PortGuardian.sweep() fires
### Expected
- Docker Desktop is NOT killed; the port-forwarding process is accepted as legitimate
### Actual (before fix)
- Docker Desktop is killed because PortGuardian treats it as an unexpected process on the gateway port
## Evidence
- [x] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
| Test | Status |
|------|--------|
| `portGuardianRemoteModeDoesNotKillDocker` | ✅ Exact #6755 scenario regression |
| `portGuardianLocalModeStillRejectsUnexpected` | ✅ Local mode behavior preserved |
| `portGuardianRemoteModeReportAcceptsAnyListener` | ✅ buildReport path verified |
| All 16 tests in `LowCoverageHelperTests` | ✅ Pass locally |
## Human Verification (required)
- Verified scenarios: All 3 new regression tests pass locally; all 16 existing tests in `LowCoverageHelperTests` continue to pass
- Edge cases checked: Local mode still rejects unexpected processes; remote mode accepts Docker, SSH, and Podman processes
- What you did **not** verify: Production remote mode environment with actual Docker Desktop (tested via unit tests with `_testIsExpected()` helper)
## Compatibility / Migration
- Backward compatible? `Yes`
- Config/env changes? `No`
- Migration needed? `No`
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Revert the `sweep()` remote-mode guard and `isExpected()` changes in `PortGuardian.swift`
- Files/config to restore: `PortGuardian.swift`
- Known bad symptoms reviewers should watch for: Unexpected processes surviving on the gateway port in local mode
## Risks and Mitigations
- Risk: In remote mode, a rogue (non-Docker/SSH) process holding the gateway port will no longer be killed
- Mitigation: This is by design — remote mode assumes the port is externally managed, providing defense-in-depth. Local mode retains full kill behavior.
## Changes
| File | Change |
|------|--------|
| `PortGuardian.swift` | `isExpected()`: refactor to `static`, accept any process on gateway port in remote mode |
| `PortGuardian.swift` | `sweep()`: add safety net — skip kill in remote mode |
| `PortGuardian.swift` | `buildReport()`: update remote mode description and predicate |
| `PortGuardian.swift` | Add `_testIsExpected()` test helper in `#if DEBUG` |
| `LowCoverageHelperTests.swift` | Add 3 regression tests |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
Fixed `PortGuardian` from killing Docker Desktop (and other port-forwarding processes) when the gateway runs in remote mode. In remote mode, the gateway port is externally managed (via Docker port-forward, SSH tunnel, or direct WebSocket connection), so any process holding the port should be accepted rather than terminated.
**Key changes:**
- `isExpected()`: Refactored to `static` method, now returns `true` for any process on gateway port in remote mode (previously only accepted SSH)
- `sweep()`: Added defense-in-depth guard that skips kill entirely in remote mode before attempting termination
- `buildReport()`: Updated remote mode description from "SSH tunnel to remote gateway" to "Remote gateway (SSH tunnel, Docker, or direct)" and changed predicate to accept any listener
- Added comprehensive test coverage with 3 regression tests validating the fix
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with no issues found
- The fix is well-designed with defense-in-depth (two separate checks in different code paths), comprehensive test coverage validates all three scenarios (remote mode accepts Docker/SSH/Podman, local mode still rejects unexpected processes, buildReport path works correctly), and the logic is sound - remote mode port forwarding is externally managed so any process holding the port is legitimate
- No files require special attention
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#8478: Security: remove --allow-unconfigured from default Dockerfile CMD
by uttej-badwane · 2026-02-04
76.6%
#21784: fix(security): restrict gateway ports to loopback by default
by smilekyra · 2026-02-20
76.4%
#9999: Docker: fix token mismatch and add dev setup workflow
by benclarkeio · 2026-02-06
76.1%
#11147: fix(daemon): stop gateway by port when no daemon service is active
by jasonthewhale · 2026-02-07
75.4%
#12504: fix: allow docker cli container to connect to gateway
by bvanderdrift · 2026-02-09
75.1%
#6638: fix: make Dockerfile work on container platforms (Render, Railway, ...
by kaizen403 · 2026-02-01
75.0%
#22110: fix(tools): prefer loopback for internal tool-to-gateway RPC calls
by pierreeurope · 2026-02-20
74.9%
#21305: Fix/proxy-ip-allow-list
by janaka · 2026-02-19
74.8%
#9190: feat(docker): Add autonomous container self-restart and runtime pac...
by alexdredmon · 2026-02-05
74.5%
#6770: fix(gateway): protect host-local transport fields from config.patch
by ryx2 · 2026-02-02
74.5%