Skip to content

feat(disguise): harden container spoofing and add in-container OAuth browser - #11

Open
kamikakushi wants to merge 1 commit into
ZaneL1u:mainfrom
kamikakushi:feat/container-spoof-hardening
Open

kamikakushi wants to merge 1 commit into
ZaneL1u:mainfrom
kamikakushi:feat/container-spoof-hardening

Conversation

@kamikakushi

Copy link
Copy Markdown

背景

对 claude 2.1.304(bun 编译二进制)做了逆向分析,确认了其实际采集路径,并据此补强容器伪装层。同时解决 claude /login 浏览器认证无法走容器网络的问题。

关键事实(逆向结论)

  • NODE_OPTIONS=--require 对 bun 编译版无效,BUN_OPTIONS=--preload 是唯一有效注入通道
  • bun 内建网络栈(fetch/dns/net/tls)无法在 JS 层 patch,遥测拦截依赖 sing-box + nft
  • claude 直接读 /proc/self/cgroup(.includes("docker"))、/proc/self/mountinfo、statSync("/.dockerenv"),并跑 uname -s/-r/-m 采集内核
  • OAuth 回调指向容器内 localhost:随机端口,本机浏览器无法回调;且本机认证流量出口 IP 与容器 API 不一致,触发关联风控

改动内容

tools/spoof-fingerprint.js(重写补强)

  • 补 /proc/self/cgroup 改写(此前只改 /proc/1/cgroup,claude 有直接读 self 的路径)
  • /proc/self/mountinfo 改写:overlay 根挂载重建为 ext4,丢弃 docker 卷挂载行
  • statSync/accessSync 对 /.dockerenv、/run/.containerenv 抛 ENOENT(此前只有 existsSync)
  • 拦截 uname(-s/-r/-m/-n/-a),输出与 os.release() 伪装值一致
  • 时区 patch:Intl.DateTimeFormat().resolvedOptions().timeZone 与 Date.prototype.getTimezoneOffset 指向同一目标(SPOOF_TIMEZONE → TZ → America/New_York)
  • 补 os.availableParallelism 伪装(双注入实测发现泄漏宿主核数)
  • machine-id 改为透传(entrypoint 已提供唯一持久值,JS 假值与 shell 读取不一致)
  • safeAssign 降级赋值:bun 对 os.tmpdir 等内置导出只读冻结,直接赋值导致 claude 启动崩溃(新旧版本共同的雷,首次在 bun 实测暴露)

managed-user 镜像 / entrypoint

  • machine-id 持久化到 /var/lib/claude-persist(原 hostname+uptime 派生,每次重启漂移 → 遥测端看到"频繁换设备")
  • DISGUISE_TZ 落地 /etc/timezone + /etc/localtime + 环境(Date/Intl/shell 三层一致)
  • cgroup 内存配额导出为 SPOOF_TOTAL_MEM(os.totalmem() 返回宿主物理内存,按配额伪装)
  • BUN_OPTIONS=--preload 三通道注入(PID 1 进程环境 + entrypoint 生成的 profile.d/zz-disguise.sh + /etc/environment)——镜像 UsePAM=no,/etc/environment 单通道不进 login shell
  • 修复 RUN_USER: unbound variable(伪装函数提前到用户变量初始化之前执行)
  • 镜像装入 spoof 模块 + 显式安装 tzdata

容器内 OAuth 浏览器

  • 新增 browser-open.sh(/usr/local/bin/cc-browser-open):claude 经 $BROWSER 在 VNC 桌面 Chromium 打开授权页,认证流量与 API 同走 sing-box 出口,回调走容器内 localhost
  • 修复 launch-chromium.sh 带 URL 参数时丢失全部 WebRTC 防泄漏 flags 的问题

其他

  • claude-spoofed.sh / dns-guard.js:标注实测局限,避免误判有进程内网络拦截
  • tools/spoof-selftest.js:22 项自测

验证

  • node tools/spoof-selftest.js:22/22 通过
  • 真实 Linux 容器端到端:SSH 登录会话环境注入齐全(TZ/BUN_OPTIONS/SPOOF_*)、date 显示目标时区、machine-id 跨 docker restart 稳定、claude 实际加载伪装身份(hostname/platform/timezone/cpus/totalMem=配额值/release)、$BROWSER 调用后 Chromium 弹出于 VNC 桌面、策略路由确认浏览器流量走 tun0
  • 顺带发现:cgroup v2 + private cgroupns 下 /proc/self/cgroup 本来就是 0::/,相关 patch 属双保险

遗留(另行处理)

  • 控制面按出口 IP 地理下发 DISGUISE_TZ(当前缺省 America/New_York)

…browser

spoof-fingerprint.js (informed by reverse-engineering claude 2.1.304):
- rewrite /proc/self/cgroup (claude reads it directly) and
  /proc/self/mountinfo (overlay root rebuilt as ext4)
- statSync/accessSync on /.dockerenv and /run/.containerenv throw ENOENT
- intercept uname -s/-r/-m/-n/-a consistent with spoofed os.release
- patch Intl timeZone and Date.prototype.getTimezoneOffset in tandem
  (SPOOF_TIMEZONE, fallback to TZ, default America/New_York)
- spoof os.availableParallelism (was leaking host core count)
- pass machine-id through: entrypoint already provides a unique,
  persistent value; JS-layer fakes disagreed with shell reads
- safeAssign fallback: bun freezes some os builtins (os.tmpdir
  assignment crashed the claude binary on startup)

managed-user entrypoint / image:
- machine-id persisted to /var/lib/claude-persist (stable across
  restarts; was hostname+uptime and changed every boot)
- DISGUISE_TZ applied to /etc/timezone + /etc/localtime + env
- cgroup memory quota exported as SPOOF_TOTAL_MEM so os.totalmem()
  does not leak host RAM
- BUN_OPTIONS=--preload injected via PID1 env + generated profile.d
  + /etc/environment (UsePAM=no skips pam_env)
- ship spoof module in image; add tzdata
- fix RUN_USER unbound variable when disguise runs before user setup

In-container OAuth browser:
- browser-open.sh installed as cc-browser-open; BROWSER exported so
  claude /login opens the auth page in the VNC desktop Chromium:
  auth traffic shares the sing-box egress IP and the localhost
  callback stays inside the container
- launch-chromium.sh: keep WebRTC leak-protection flags when a URL
  argument is passed (was exec chromium "$@" bare)

tools:
- claude-spoofed.sh: document that BUN_OPTIONS is the only working
  injection channel for bun-compiled claude
- dns-guard.js: document bun-runtime ineffectiveness (network layer
  is built-in; telemetry blocking relies on sing-box + nft)
- spoof-selftest.js: 22-case self-test

Verified end-to-end in a real Linux container: env injection in SSH
login shells, machine-id stable across docker restart, date showing
target timezone, claude loading the spoof identity, chromium opening
on the VNC display, egress policy routing via tun0.

This branch has not been deployed

No deployments
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