anthropics / anthropics/claude-code
Sockets directory is still first-come after 2.1.248, and a local account can squat both /tmp/cc-socks and the per-uid fallback
- Ngôn ngữ chính
- Python
- Star
- 145k
- Fork
- 23.1k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
## Summary
Version 2.1.248 fixed the symptom reported in #89401. A second account on a shared host now keeps cross-session messaging, because it falls back to `/tmp/cc-socks-`. Two problems remain.
1. The sockets directory is still first-come. `/tmp/cc-socks` belongs to whichever account starts Claude Code first after each `/tmp` clear. The directory an account uses therefore changes from boot to boot.
2. Any unprivileged local account can take both names. The fallback has no fallback, so messaging goes off again.
This report also documents a workaround, because the current one in the error text does not survive case 2.
## Environment
- Claude Code 2.1.252, native install, macOS 25.6.0 (arm64)
- Two local accounts, uid 501 and uid 502
- `XDG_RUNTIME_DIR` unset, `CLAUDE_CODE_TMPDIR` unset
## 1. First-come ownership
The primary path keeps the shared name:
```js
var Y = 103;
function Yur(){
let e = a.XDG_RUNTIME_DIR || ly(),
t = ue(F(e, "cc-socks", `${process.pid}.sock`));
if (Buffer.byteLength(t) <= Y) return t;
return nNn();
}
```
`/tmp` is world-writable and sticky. The first account to start Claude Code creates `/tmp/cc-socks` at mode 0700. That account keeps it. Every other account fails the owner check and takes the per-uid path. macOS clears `/tmp`, so the winner can change after a reboot.
Peer discovery does not break. Each session writes its absolute socket path into `~/.claude/sessions/.json`:
```json
"pid": "45417",
"messagingSocketPath": "/tmp/claude-501/cc-socks/45417.sock",
"peerProtocol": "1"
```
Peers read that file, so they never resolve the directory by convention. The cost is diagnostic, not functional. An account cannot predict its own socket directory, and support answers now depend on login order.
The uid suffix costs 4 bytes out of the 103-byte budget. Make `cc-socks-` the default. This removes the shared name, the boot-order dependency, and the first squat target below.
## 2. A local account can squat both names
The refusal path tries the per-uid directory once, then stops:
```js
try { await Re(o) }
catch (m) {
let k = un(m) ? await XGe() : void 0,
u = k === void 0 ? void 0 : ue(nNn(k)),
_ = u === void 0 ? void 0 : F(u, "..");
if (u === void 0 || _ === void 0 || _ === o) return $e(o, m, De(m));
n(`[uds-messaging] sockets directory ${o} refused (...); trying the per-uid fallback ${_}`, {level:"warn"});
try { await Re(_) } catch (f) { return $e(_, f, De(f)) }
e = u; we("primary_dir_refused_fell_back")
}
```
`nNn()` builds the fallback from a fixed uid, so the target name is public and predictable:
```js
function nNn(e = process.getuid?.() ?? 0){
let t = a.TERMUX_VERSION ? a.PREFIX : void 0,
i = t ? F(t, "tmp") : "/tmp";
return F(i, `cc-socks-${e}`, `${process.pid}.sock`);
}
```
### Steps to reproduce
1. As any local account, and before the target account starts Claude Code, run these two commands. Replace 501 with the target uid.
```
mkdir -m 700 -p /tmp/cc-socks
mkdir -m 700 -p /tmp/cc-socks-501
```
2. As uid 501, start Claude Code.
3. Read the debug log.
### Result
The primary directory fails the owner check. The per-uid directory fails the same check. The code calls `$e()` and cross-session messaging turns off for that account.
Both directories survive a `/tmp` clear if the other account recreates them from a login item or a launchd agent.
### Scope
This is denial of service only. The owner check and the `peerToken` file still stop the squatting account from receiving anyone else's messages. The attacker needs a local login on the same host. The realistic targets are shared Macs, build hosts, and CI runners with several service accounts.
### Suggested fix
Try a third location before giving up. A directory under the user's own home works, because another account cannot create it. Report the refusal in `/status` in both cases.
## 3. Workaround
Set `XDG_RUNTIME_DIR` to a private directory that you own:
```
XDG_RUNTIME_DIR=/tmp/claude-501
```
Claude Code already creates `/tmp/claude-` at mode 0700 for its own temp files, and validates it. The directory exists and passes the checks. The resulting socket path is 35 bytes, which is well inside the 103-byte limit.
This also defeats the squat in case 2, because the primary path no longer resolves to `/tmp/cc-socks`.
Two notes on the workaround:
- Set it in your shell profile or in user settings. Version 2.1.251 stopped project-level `.claude/settings.json` from setting `CLAUDE_CODE_TMPDIR`, `TMPDIR`, `TMP`, and `TEMP`.
- The fallback ignores both `XDG_RUNTIME_DIR` and `CLAUDE_CODE_TMPDIR`, as `nNn()` above shows. If your own directory is ever refused, the code sends you to `/tmp` anyway. The error text asks the user to point these variables at a private directory. The fallback should honor the same base.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Hướng nghiên cứu
Start by locating Yur(), nNn(), and the refusal path shown in the issue, then trace how XDG_RUNTIME_DIR and CLAUDE_CODE_TMPDIR are resolved. Reproduce the two-account commands and verify that socket selection no longer depends on first-come /tmp names, a private fallback is attempted before failure, and /status reports both refusals.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript, macos
- Lĩnh vực
- cli, operating-systems, security
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 52/100