claude-cac 1.5.1-beta.2 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,18 @@
41
41
  - **配置继承** — `--clone` 从宿主或其他环境继承配置,`~/.cac/settings.json` 全局偏好
42
42
  - **零配置** — 无需 setup,首次使用自动初始化
43
43
 
44
+ ### 注意事项
45
+
46
+ > **封号风险说明**:cac 提供设备指纹层保护(UUID、主机名、MAC、遥测阻断、配置隔离),但**无法影响账号层风险**——包括 OAuth 账号本身、支付方式指纹、IP 信誉评分,以及 Anthropic 的服务端封禁决策。封号是账号层问题,cac 对此无能为力。详见 [封号风险 FAQ](https://cac.nextmind.space/docs/zh/guides/ban-risk)。
47
+
48
+ > **代理工具冲突**:如果本地启动了 Clash、Shadowrocket、Surge、sing-box 等代理/VPN 工具,建议在使用 cac 时先关闭。TUN 模式兼容性仍属实验性功能。即使发生冲突,cac 也会自动停止连接(fail-closed),**不会泄露你的真实 IP**。
49
+
50
+ - **首次登录**:启动 `claude` 后,输入 `/login` 完成 OAuth 授权
51
+ - **安全验证**:随时运行 `cac env check` 确认隐私保护状态,也可以 `which claude` 确认使用的是 cac 托管的 claude
52
+ - **自动安全检查**:每次启动 Claude Code 会话时,cac 会快速检查环境。如有异常会终止会话,不会发送任何数据
53
+ - **网络稳定性**:流量严格走代理——代理断开时流量完全停止,不会回退直连。内置心跳检测和自动恢复,断线后无需手动重启
54
+ - **IPv6**:建议系统级关闭,防止真实地址泄露
55
+
44
56
  ### 安装
45
57
 
46
58
  ```bash
@@ -196,16 +208,6 @@ cac docker port 6287 # 端口转发
196
208
 
197
209
  代理格式:`ip:port:user:pass`(SOCKS5)、`ss://...`、`vmess://...`、`vless://...`、`trojan://...`
198
210
 
199
- ### 注意事项
200
-
201
- > **代理工具冲突**:如果本地启动了 Clash、Shadowrocket、Surge、sing-box 等代理/VPN 工具,建议在使用 cac 时先关闭。TUN 模式兼容性仍属实验性功能。即使发生冲突,cac 也会自动停止连接(fail-closed),**不会泄露你的真实 IP**。
202
-
203
- - **首次登录**:启动 `claude` 后,输入 `/login` 完成 OAuth 授权
204
- - **安全验证**:随时运行 `cac env check` 确认隐私保护状态,也可以 `which claude` 确认使用的是 cac 托管的 claude
205
- - **自动安全检查**:每次启动 Claude Code 会话时,cac 会快速检查环境。如有异常会终止会话,不会发送任何数据
206
- - **网络稳定性**:流量严格走代理——代理断开时流量完全停止,不会回退直连。内置心跳检测和自动恢复,断线后无需手动重启
207
- - **IPv6**:建议系统级关闭,防止真实地址泄露
208
-
209
211
  ---
210
212
 
211
213
  <a id="english"></a>
@@ -224,6 +226,18 @@ cac docker port 6287 # 端口转发
224
226
  - **Config inheritance** — `--clone` inherits config from host or other envs, `~/.cac/settings.json` for global preferences
225
227
  - **Zero config** — no setup needed, auto-initializes on first use
226
228
 
229
+ ### Notes
230
+
231
+ > **Account ban notice**: cac provides device fingerprint layer protection (UUID, hostname, MAC, telemetry blocking, config isolation), but **cannot affect account-layer risks** — including your OAuth account, payment method fingerprint, IP reputation score, or Anthropic's server-side ban decisions. Account bans are an account-layer issue that cac does not address. See the [Ban Risk FAQ](https://cac.nextmind.space/docs/guides/ban-risk) for details.
232
+
233
+ > **Proxy tool conflicts**: If you have Clash, Shadowrocket, Surge, sing-box or other proxy/VPN tools running locally, turn them off before using cac. TUN-mode compatibility is still experimental. Even if a conflict occurs, cac will fail-closed — **your real IP is never exposed**.
234
+
235
+ - **First login**: Run `claude`, then type `/login`. Health check is automatically bypassed.
236
+ - **Verify your setup**: Run `cac env check` anytime. Use `which claude` to confirm you're using the cac-managed wrapper.
237
+ - **Automatic safety checks**: Every new Claude Code session runs a quick cac check. If anything is wrong, the session is terminated before any data is sent.
238
+ - **Network resilience**: Traffic is strictly routed through your proxy. If the proxy drops, traffic stops entirely — no fallback to direct connection. Built-in heartbeat detection and auto-recovery — no manual restart needed after disconnections.
239
+ - **IPv6**: Recommend disabling system-wide to prevent real address exposure.
240
+
227
241
  ### Install
228
242
 
229
243
  ```bash
@@ -371,16 +385,6 @@ cac docker port 6287 # port forwarding
371
385
 
372
386
  Proxy formats: `ip:port:user:pass` (SOCKS5), `ss://...`, `vmess://...`, `vless://...`, `trojan://...`
373
387
 
374
- ### Notes
375
-
376
- > **Proxy tool conflicts**: If you have Clash, Shadowrocket, Surge, sing-box or other proxy/VPN tools running locally, turn them off before using cac. TUN-mode compatibility is still experimental. Even if a conflict occurs, cac will fail-closed — **your real IP is never exposed**.
377
-
378
- - **First login**: Run `claude`, then type `/login`. Health check is automatically bypassed.
379
- - **Verify your setup**: Run `cac env check` anytime. Use `which claude` to confirm you're using the cac-managed wrapper.
380
- - **Automatic safety checks**: Every new Claude Code session runs a quick cac check. If anything is wrong, the session is terminated before any data is sent.
381
- - **Network resilience**: Traffic is strictly routed through your proxy. If the proxy drops, traffic stops entirely — no fallback to direct connection. Built-in heartbeat detection and auto-recovery — no manual restart needed after disconnections.
382
- - **IPv6**: Recommend disabling system-wide to prevent real address exposure.
383
-
384
388
  ---
385
389
 
386
390
  <div align="center">
package/cac CHANGED
@@ -11,7 +11,7 @@ VERSIONS_DIR="$CAC_DIR/versions"
11
11
  # ── utils: colors, read/write, UUID, proxy parsing ───────────────────────
12
12
 
13
13
  # shellcheck disable=SC2034 # used in build-concatenated cac script
14
- CAC_VERSION="1.5.1-beta.2"
14
+ CAC_VERSION="1.5.2"
15
15
 
16
16
  _read() { [[ -f "$1" ]] && tr -d '[:space:]' < "$1" || echo "${2:-}"; }
17
17
  _die() { printf '%b\n' "$(_red "error:") $*" >&2; exit 1; }
@@ -56,10 +56,23 @@ _gen_uuid() {
56
56
  fi
57
57
  }
58
58
  _new_uuid() { _gen_uuid | tr '[:lower:]' '[:upper:]'; }
59
- _new_sid() { _gen_uuid | tr '[:upper:]' '[:lower:]'; }
60
59
  _new_user_id() { python3 -c "import os; print(os.urandom(32).hex())" || _die "python3 required"; }
61
60
  _new_machine_id() { _gen_uuid | tr -d '-' | tr '[:upper:]' '[:lower:]'; }
62
- _new_hostname() { echo "host-$(_gen_uuid | cut -d- -f1 | tr '[:upper:]' '[:lower:]')"; }
61
+ _new_hostname() {
62
+ local -a _first_names=(
63
+ "James" "John" "Robert" "Michael" "William" "David" "Richard" "Joseph"
64
+ "Thomas" "Charles" "Daniel" "Matthew" "Anthony" "Donald" "Mark" "Paul"
65
+ "Steven" "Andrew" "Kenneth" "Joshua" "Kevin" "Brian" "George" "Timothy"
66
+ "Emma" "Olivia" "Sophia" "Isabella" "Mia" "Charlotte" "Amelia" "Harper"
67
+ "Evelyn" "Abigail" "Emily" "Elizabeth" "Sofia" "Avery" "Ella" "Scarlett"
68
+ "Liam" "Noah" "Oliver" "Elijah" "Lucas" "Mason" "Ethan" "Aiden"
69
+ "Alex" "Ryan" "Tyler" "Jordan" "Taylor" "Morgan" "Casey" "Riley"
70
+ )
71
+ local -a _models=("MacBook-Pro" "MacBook-Air" "MacBook-Pro" "MacBook-Pro")
72
+ local _name="${_first_names[$((RANDOM % ${#_first_names[@]}))]}"
73
+ local _model="${_models[$((RANDOM % ${#_models[@]}))]}"
74
+ echo "${_name}s-${_model}.local"
75
+ }
63
76
  _new_mac() { printf '02:%02x:%02x:%02x:%02x:%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)); }
64
77
  _new_git_remote() { echo "https://github.com/user-$(_gen_uuid | cut -d- -f1)/project-$(_gen_uuid | cut -d- -f2).git"; }
65
78
  _new_git_email() { echo "user-$(_gen_uuid | cut -d- -f1 | tr '[:upper:]' '[:lower:]')@users.noreply.github.com"; }
@@ -382,20 +395,6 @@ _remove_path_from_rc() {
382
395
  fi
383
396
  }
384
397
 
385
- _update_statsig() {
386
- local sid="$1"
387
- local config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
388
- local statsig="$config_dir/statsig"
389
- [[ -d "$statsig" ]] || return 0
390
- local found=false
391
- for f in "$statsig"/statsig.stable_id.*; do
392
- [[ -f "$f" ]] && { printf '"%s"' "$sid" > "$f"; found=true; }
393
- done
394
- if [[ "$found" == "false" ]]; then
395
- printf '"%s"' "$sid" > "$statsig/statsig.stable_id.local"
396
- fi
397
- }
398
-
399
398
  _update_claude_json_user_id() {
400
399
  local user_id="$1"
401
400
  local config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
@@ -1156,19 +1155,6 @@ if [[ -n "$PROXY" ]]; then
1156
1155
  fi
1157
1156
  fi
1158
1157
 
1159
- # inject statsig stable_id
1160
- if [[ -f "$_env_dir/stable_id" ]]; then
1161
- _sid=$(tr -d '[:space:]' < "$_env_dir/stable_id")
1162
- _config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
1163
- if [[ -d "$_config_dir/statsig" ]]; then
1164
- _sid_found=false
1165
- for _f in "$_config_dir/statsig"/statsig.stable_id.*; do
1166
- [[ -f "$_f" ]] && { printf '"%s"' "$_sid" > "$_f"; _sid_found=true; }
1167
- done
1168
- [[ "$_sid_found" == "false" ]] && printf '"%s"' "$_sid" > "$_config_dir/statsig/statsig.stable_id.local"
1169
- fi
1170
- fi
1171
-
1172
1158
  # inject env vars — proxy (only when proxy is configured)
1173
1159
  if [[ -n "$PROXY" ]]; then
1174
1160
  export _CAC_PROXY="$PROXY"
@@ -1253,6 +1239,13 @@ fi
1253
1239
  # ── persona (Docker/server environment spoofing) ──
1254
1240
  if [[ -f "$_env_dir/persona" ]]; then
1255
1241
  _persona=$(tr -d '[:space:]' < "$_env_dir/persona")
1242
+ # Clear all high-priority detectTerminal() variables before injecting persona,
1243
+ # so real env vars from the host (e.g. CURSOR_TRACE_ID in Cursor) don't override.
1244
+ unset CURSOR_TRACE_ID VSCODE_GIT_ASKPASS_MAIN TERMINAL_EMULATOR VisualStudioVersion
1245
+ unset ITERM_SESSION_ID TERM_PROGRAM __CFBundleIdentifier
1246
+ unset TMUX STY KONSOLE_VERSION GNOME_TERMINAL_SERVICE XTERM_VERSION VTE_VERSION
1247
+ unset TERMINATOR_UUID KITTY_WINDOW_ID ALACRITTY_LOG TILIX_ID WT_SESSION
1248
+ unset MSYSTEM ConEmuANSI ConEmuPID ConEmuTask WSL_DISTRO_NAME
1256
1249
  export TERM="xterm-256color"
1257
1250
  case "$_persona" in
1258
1251
  macos-vscode)
@@ -1318,6 +1311,7 @@ fi
1318
1311
  [[ -f "$_env_dir/mac_address" ]] && export CAC_MAC=$(tr -d '[:space:]' < "$_env_dir/mac_address")
1319
1312
  [[ -f "$_env_dir/machine_id" ]] && export CAC_MACHINE_ID=$(tr -d '[:space:]' < "$_env_dir/machine_id")
1320
1313
  export CAC_USERNAME="user-$(echo "$_name" | cut -c1-8)"
1314
+ export USER="$CAC_USERNAME" LOGNAME="$CAC_USERNAME"
1321
1315
  if [[ -f "$CAC_DIR/fingerprint-hook.js" ]]; then
1322
1316
  case "${NODE_OPTIONS:-}" in
1323
1317
  *fingerprint-hook.js*) ;;
@@ -1736,7 +1730,6 @@ _env_cmd_create() {
1736
1730
  mkdir -p "$env_dir"
1737
1731
  [[ -n "$proxy_url" ]] && echo "$proxy_url" > "$env_dir/proxy"
1738
1732
  echo "$(_new_uuid)" > "$env_dir/uuid"
1739
- echo "$(_new_sid)" > "$env_dir/stable_id"
1740
1733
  echo "$(_new_user_id)" > "$env_dir/user_id"
1741
1734
  echo "$(_new_machine_id)" > "$env_dir/machine_id"
1742
1735
  echo "$(_new_hostname)" > "$env_dir/hostname"
@@ -1830,7 +1823,6 @@ MERGE_EOF
1830
1823
  if [[ -d "$env_dir/.claude" ]]; then
1831
1824
  export CLAUDE_CONFIG_DIR="$env_dir/.claude"
1832
1825
  fi
1833
- _update_statsig "$(_read "$env_dir/stable_id")" 2>/dev/null || true
1834
1826
  _update_claude_json_user_id "$(_read "$env_dir/user_id")" 2>/dev/null || true
1835
1827
 
1836
1828
  local elapsed; elapsed=$(_timer_elapsed)
@@ -1927,7 +1919,6 @@ _env_cmd_activate() {
1927
1919
  export CLAUDE_CONFIG_DIR="$ENVS_DIR/$name/.claude"
1928
1920
  fi
1929
1921
 
1930
- _update_statsig "$(_read "$ENVS_DIR/$name/stable_id")"
1931
1922
  _update_claude_json_user_id "$(_read "$ENVS_DIR/$name/user_id")"
1932
1923
 
1933
1924
  # Relay lifecycle
@@ -99,9 +99,57 @@ if (fakeMachineId) {
99
99
 
100
100
  // --- Repository fingerprint (rh) interception ---
101
101
  // Claude Code computes rh = SHA256(normalized_git_remote_url).hex.slice(0,16)
102
- // and sends it with every 1p_event — cross-account linkage vector
102
+ // and sends it with every 1p_event — cross-account linkage vector.
103
+ //
104
+ // CC 2.1.88 reads the remote URL via gitFilesystem.ts which calls
105
+ // fs.readFileSync('.git/config') directly — NOT via git subprocess.
106
+ // We intercept both paths for defense in depth.
103
107
  const fakeGitRemote = process.env.CAC_FAKE_GIT_REMOTE;
104
108
  if (fakeGitRemote) {
109
+ // Path 1: intercept .git/config reads (primary path in CC 2.1.88)
110
+ // Replaces the [remote "origin"] url line with our fake remote URL.
111
+ function isGitConfigPath(p) {
112
+ var s = typeof p === 'string' ? p : (p && p.toString ? p.toString() : '');
113
+ return s === '.git/config' || s.endsWith('/.git/config') || s.endsWith('\\.git\\config');
114
+ }
115
+ function patchGitConfig(content) {
116
+ var str = typeof content === 'string' ? content : content.toString('utf8');
117
+ // Replace url = <anything> under [remote "origin"] section
118
+ str = str.replace(
119
+ /(\[remote\s+"origin"\][^\[]*?url\s*=\s*)[^\n]*/,
120
+ '$1' + fakeGitRemote
121
+ );
122
+ return str;
123
+ }
124
+
125
+ // Patch readFileSync (sync path used by gitFilesystem.ts)
126
+ var _origReadFileSyncRh = fs.readFileSync;
127
+ fs.readFileSync = function(path, options) {
128
+ var result = _origReadFileSyncRh.apply(fs, arguments);
129
+ if (isGitConfigPath(path)) {
130
+ var patched = patchGitConfig(result);
131
+ return fakeResult(options, patched);
132
+ }
133
+ return result;
134
+ };
135
+
136
+ // Patch fs.promises.readFile (async path)
137
+ try {
138
+ var fspRh = require('fs').promises || require('fs/promises');
139
+ if (fspRh && fspRh.readFile) {
140
+ var _origPromiseReadFileRh = fspRh.readFile.bind(fspRh);
141
+ fspRh.readFile = function(path, options) {
142
+ if (isGitConfigPath(path)) {
143
+ return _origPromiseReadFileRh(path, options).then(function(content) {
144
+ return fakeResult(options, patchGitConfig(content));
145
+ });
146
+ }
147
+ return _origPromiseReadFileRh(path, options);
148
+ };
149
+ }
150
+ } catch (_) {}
151
+
152
+ // Path 2: intercept git subprocess calls (fallback / older CC versions)
105
153
  const GIT_REMOTE_PATTERNS = [
106
154
  /git\s+remote\s+get-url/i,
107
155
  /git\s+remote\s+-v/i,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cac",
3
- "version": "1.5.1-beta.2",
3
+ "version": "1.5.2",
4
4
  "description": "Isolate, protect, and manage your Claude Code — versions, environments, identity, and proxy.",
5
5
  "bin": {
6
6
  "cac": "cac"