fix: Limit Unix IPC endpoints to the current OS user#1793
Conversation
Use an owner-only per-user socket directory and validate every existing path before use. This prevents another OS user from precreating or replacing the endpoint while preserving the not-running recovery path when Unity has not created it yet.
The user-owned Unix endpoint changes the rendezvous contract between the Unity package and project runner. Advance both protocol declarations together while preserving release-managed version pins.
📝 WalkthroughWalkthroughUnix IPC endpoints now use UID-scoped paths, explicit Unix filesystem security validation, restricted socket permissions, protected listener cleanup, CLI pre-dial checks, updated endpoint contracts, and expanded Unity and Go test coverage. ChangesUnix IPC endpoint security
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant UnityIPC
participant IPCEndpoint
participant UnixSocket
CLI->>UnityIPC: Dial endpoint
UnityIPC->>IPCEndpoint: Validate parent and endpoint directory
IPCEndpoint-->>UnityIPC: Return validation result
UnityIPC->>UnixSocket: Dial only after successful validation
UnixSocket-->>UnityIPC: Return connection
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Packages/src/Editor/Infrastructure/BridgeTransportListener.cs`:
- Around line 120-125: Update the restriction-failure branch in the bridge
transport listener setup to invoke the existing secured shutdown/cleanup path
that removes the bound socket, rather than only closing and nulling `_listener`;
preserve throwing the IOException with socketModeResult.ErrorMessage afterward.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1e6321df-2255-4c0a-8beb-aba9543f3a1c
⛔ Files ignored due to path filters (3)
Assets/Tests/Editor/UnixEndpointSecurityTests.cs.metais excluded by none and included by nonePackages/src/Editor/Infrastructure/UnixEndpointSecurity.cs.metais excluded by none and included by nonePackages/src/Editor/Infrastructure/UnixNativeFileSystem.cs.metais excluded by none and included by none
📒 Files selected for processing (23)
Assets/Tests/Editor/BridgeTransportEndpointTests.csAssets/Tests/Editor/BridgeTransportListenerTests.csAssets/Tests/Editor/UnixEndpointSecurityTests.csPackages/src/Editor/Domain/CliConstants.csPackages/src/Editor/Infrastructure/BridgeTransportEndpoint.csPackages/src/Editor/Infrastructure/BridgeTransportListener.csPackages/src/Editor/Infrastructure/UnixEndpointSecurity.csPackages/src/Editor/Infrastructure/UnixNativeFileSystem.cscli/common/clicontract/contract.jsoncli/common/errors/unity_endpoint_not_created.gocli/common/errors/unity_endpoint_not_created_test.gocli/common/project/endpoint_contract_test.gocli/common/project/project.gocli/common/project/project_test.gocli/common/project/unix_endpoint_security.gocli/common/project/unix_endpoint_security_test.gocli/common/project/unix_endpoint_security_unix.gocli/common/project/unix_endpoint_security_unix_test.gocli/common/project/unix_endpoint_security_windows.gocli/dispatcher/internal/dispatcher/dispatcher_test.gocli/dispatcher/shared-inputs-stamp.jsoncli/project-runner/shared-inputs-stamp.jsontests/contracts/endpoint_contract.json
| UnixEndpointSecurityResult socketModeResult = _securityPolicy.RestrictSocket(Endpoint.Path); | ||
| if (!socketModeResult.Success) | ||
| { | ||
| _listener.Close(); | ||
| _listener = null; | ||
| throw new IOException(socketModeResult.ErrorMessage); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Remove the bound socket when restriction fails.
Closing and nulling _listener leaves Endpoint.Path behind, and subsequent Stop() returns without cleanup. Use the secured shutdown path before throwing.
Proposed fix
if (!socketModeResult.Success)
{
- _listener.Close();
- _listener = null;
+ Stop();
throw new IOException(socketModeResult.ErrorMessage);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| UnixEndpointSecurityResult socketModeResult = _securityPolicy.RestrictSocket(Endpoint.Path); | |
| if (!socketModeResult.Success) | |
| { | |
| _listener.Close(); | |
| _listener = null; | |
| throw new IOException(socketModeResult.ErrorMessage); | |
| UnixEndpointSecurityResult socketModeResult = _securityPolicy.RestrictSocket(Endpoint.Path); | |
| if (!socketModeResult.Success) | |
| { | |
| Stop(); | |
| throw new IOException(socketModeResult.ErrorMessage); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Packages/src/Editor/Infrastructure/BridgeTransportListener.cs` around lines
120 - 125, Update the restriction-failure branch in the bridge transport
listener setup to invoke the existing secured shutdown/cleanup path that removes
the bound socket, rather than only closing and nulling `_listener`; preserve
throwing the IOException with socketModeResult.ErrorMessage afterward.
e278c5a
into
security/cli-distribution-hardening-integration
Summary
Previous behavior and root cause
All Unix clients used a shared
/tmp/uloopdirectory. Another OS user could precreate or replace endpoint paths before the intended Unity process used them. The initial fix validated while resolving a project, but resolution also serves pin, cache, help, and version-forwarding commands that never dial Unity.Fix and attack conditions
C# and Go derive the same
/tmp/uloop-UIDendpoint. They validate the sticky root-owned/tmptarget, require a real owner-only0700endpoint directory, and refuse symlinks, wrong ownership, special bits, and unsafe stale paths. Socket permissions are verified as0600after bind.The Go client now has one dial choke point. On Unix it validates the same endpoint policy immediately before
net.Dial; a missing private directory is typed as Unity not running and never attempts a dial. Windows enters the same choke point with a no-op validator, preserving named-pipe behavior.The Unix stat ABI is selected explicitly for macOS x64, macOS arm64, and glibc Linux x64/arm64. Unknown ABI layouts fail closed.
Verification
go test ./project ./unityipc ./ipcendpoint ./errorsgo test ./...scripts/check-go-cli.shTestProtocolVersionMatchesUnityPackaged89b9f25d0bba6f48a92d9505baeb83d59dd9878:Protocol release ordering
This PR changes the IPC rendezvous contract and advances
protocolVersionfrom 3 to 4. The PR-diff-equivalent local invocation ofcheck-protocol-minimum-versionintentionally fails until a project runner release advertising protocol 4 is published. The manually dispatched exact-head CI has no pull-request diff and therefore skips that diff-dependent reminder; it is not evidence of a passing minimum-version check.After the runner release, release-please must advance the package pin to the published runner version before the Unity package release proceeds.
Residual risk
The design protects against other OS users in the shared temporary parent. A process running as the same OS user can still manipulate that user's private directory, which is outside this multi-user boundary.