@clawos-dev/clawd 0.2.232 → 0.2.233

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +65 -75
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -41946,10 +41946,10 @@ var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u9
41946
41946
  # execPath+cliPath\uFF0C\u4FDD\u8BC1 relay \u8DDF daemon \u7248\u672C\u540C\u6B65\u3002\u4E0D\u8981\u7528\u88F8 \`clawd\`\uFF08\u8D70 $PATH \u4F1A\u6F02\uFF09\u3002
41947
41947
  \`\`\`
41948
41948
 
41949
- **A \u6388\u6743\u54EA\u4E9B\u76EE\u5F55\u4E0D\u7528\u4F60\u63D0\u524D\u8BB0**\uFF1AA \u4FA7\u7684 sshd \u62E8\u53F7\u4F1A\u5F3A\u5236\u8D70 sandbox jail\uFF0C\u8FDB\u6C99\u7BB1\u524D\u4F1A\u628A\u6388\u6743\u76EE\u5F55\u5217\u8868\u5199\u5230 stderr\uFF08\u683C\u5F0F \`[clawd-ssh-jail] Access allowed to these directories: ...\`\uFF09\u3002\u4F60\u5373\u4F7F\u731C\u9519\u8DEF\u5F84\u4E5F\u4F1A\u88AB sandbox deny\uFF0C\u4F46 stderr \u91CC\u5C31\u81EA\u5E26\u7B54\u6848\uFF0C\u8BD5\u9519\u4E00\u6B21\u5C31\u5B66\u5230 A \u6388\u6743\u4E86\u54EA\u4E9B\u76EE\u5F55\uFF0C\u4E0B\u6B21\u76F4\u63A5\u7528\u5BF9\u7684\u8DEF\u5F84\u3002
41949
+ **A \u5EFA\u8BAE\u5728\u54EA\u4E9B\u76EE\u5F55\u5E72\u6D3B**\uFF1AA \u4FA7 jail \u8FDB shell \u524D\u4F1A\u628A\u5EFA\u8BAE\u76EE\u5F55\u5217\u8868\u5199\u5230 stderr\uFF08\u683C\u5F0F \`[clawd-ssh-jail] Suggested working directories for this contact: ...\`\uFF09\u4F5C\u4E3A\u53C2\u8003\u3002\u6CE8\u610F\uFF1A**\u6CA1\u6709\u76EE\u5F55\u7EA7\u5F3A\u5236\u9650\u5236**\u2014\u2014\u4F60\u8BBF\u95EE\u7684\u662F A \u673A\u5668\u4E0A\u8BE5 SSH \u7528\u6237\u6743\u9650\u5185\u7684\u6587\u4EF6\uFF0C\u8BF7\u53EA\u5728 A \u6388\u6743/\u9700\u8981\u7684\u8303\u56F4\u5185\u64CD\u4F5C\uFF0C\u4E0D\u8981\u8D8A\u754C\u7FFB\u770B\u65E0\u5173\u76EE\u5F55\u3002
41950
41950
 
41951
41951
  **\u6CE8\u610F**\uFF1A
41952
- - \u6388\u6743\u76EE\u5F55\u6570\u636E\u6E90\u5B9E\u65F6\uFF08jail \u6BCF\u6B21\u8BFB A \u4FA7\u6700\u65B0\u914D\u7F6E\uFF09\uFF0CA \u6539\u4E86\u6388\u6743\u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u611F\u77E5
41952
+ - \u5EFA\u8BAE\u76EE\u5F55\u6570\u636E\u6E90\u5B9E\u65F6\uFF08jail \u6BCF\u6B21\u8BFB A \u4FA7\u6700\u65B0\u914D\u7F6E\uFF09\uFF0CA \u6539\u4E86 exposedDirs \u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u611F\u77E5
41953
41953
  - \u6392\u969C\uFF1A\u672C\u673A debug \u65E5\u5FD7\u5728 \`~/.clawd/log/contact-ssh.log\`\uFF08\u7528\u6237\u53EF \`tail -f\` \u770B\u6BCF\u4E00\u6B65\uFF09\uFF0C\u4F60\u51FA\u9519\u65F6\u544A\u8BC9\u7528\u6237\u67E5\u8FD9\u4E2A\u6587\u4EF6
41954
41954
  - \u5982\u679C \`ls ~/.clawd/contact-ssh-keys/\` \u4E3A\u7A7A\uFF0C\u8BF4\u660E\u8FD8\u6CA1\u6709\u5BF9\u7AEF\u6388\u6743\u4F60\u6216 key \u8FD8\u6CA1\u62C9\u5230\uFF08B \u4FA7 daemon \u6BCF 60s \u4E00\u6B21\u81EA\u52A8\u62C9\uFF09\uFF0C\u5982\u5B9E\u544A\u8BC9\u7528\u6237
41955
41955
  `;
@@ -53464,9 +53464,8 @@ var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
53464
53464
  # 真实请求(interactive shell 时为空)。
53465
53465
  #
53466
53466
  # 职责:
53467
- # 1. 读 ~/.clawd/contacts.json contact.exposedDirs
53468
- # 2. macOS 用 sandbox-exec + sbpl; Linux 用 bwrap
53469
- # 3. exec 沙箱 shell
53467
+ # 1. 读 ~/.clawd/contacts.json 校验 contact.sshAllowed,读 exposedDirs 作为提示
53468
+ # 2. 直接 exec shell(去沙箱后无目录级限制;exposedDirs echo 给对端参考)
53470
53469
 
53471
53470
  set -euo pipefail
53472
53471
 
@@ -53513,33 +53512,19 @@ for c in data.get('contacts', []):
53513
53512
  sys.exit(3)
53514
53513
  ")
53515
53514
 
53516
- if [ -z "$EXPOSED_JSON" ]; then
53517
- log_line ERROR jail.entered "contact ${"${DEVICE_ID}"} 不在 store 或 exposedDirs 为空"
53518
- echo "clawd-ssh-jail: contact not found or no exposed dirs" >&2
53519
- exit 1
53515
+ # exposedDirs 可能为空(sshAllowed=true 但未配目录)——去沙箱后不再因空目录拒绝,直接放行。
53516
+ if [ -n "$EXPOSED_JSON" ]; then
53517
+ log_line INFO jail.entered "exposedDirs 提示已加载,放行 exec shell"
53518
+ # 告知 SSH client 建议目录(发到 stderr)。去沙箱后这些只是建议,不做目录级强制限制;
53519
+ # exposedDirs 仅供对端参考,运行时读,A 改后下次拨号即感知。
53520
+ echo "[clawd-ssh-jail] Suggested working directories for this contact:" >&2
53521
+ while IFS= read -r d; do
53522
+ echo " - $d" >&2
53523
+ done <<< "$EXPOSED_JSON"
53524
+ echo "[clawd-ssh-jail] Note: no directory-level restriction is enforced." >&2
53525
+ else
53526
+ log_line INFO jail.entered "sshAllowed=true 且未配 exposedDirs,直接放行 exec shell"
53520
53527
  fi
53521
- log_line INFO jail.entered "exposedDirs 白名单已加载,进沙箱 exec shell"
53522
-
53523
- # 进沙箱前告知 SSH client 授权目录列表(发到 stderr)—— CC 或其他 SSH client 试错一次就能
53524
- # 学到 exposedDirs 具体值,无需 A 侧向 B 侧同步(授权数据实时的 single source of truth)。
53525
- # 每次拨号运行时读,A 改 exposedDirs 后 B 侧下一次拨号立即感知。
53526
- echo "[clawd-ssh-jail] Access allowed to these directories:" >&2
53527
- while IFS= read -r d; do
53528
- echo " - $d" >&2
53529
- done <<< "$EXPOSED_JSON"
53530
- echo "[clawd-ssh-jail] Anything outside these paths will be sandbox-denied." >&2
53531
-
53532
- # 校验路径安全(bash 侧二次防御)
53533
- while IFS= read -r line; do
53534
- case "$line" in
53535
- /*) : ;;
53536
- *) echo "clawd-ssh-jail: bad path: $line" >&2; exit 1 ;;
53537
- esac
53538
- case "$line" in
53539
- *[\"\'\`\$\;\|\&\(\)\{\}\[\]\<\>\*\?]*)
53540
- echo "clawd-ssh-jail: unsafe path: $line" >&2; exit 1 ;;
53541
- esac
53542
- done <<< "$EXPOSED_JSON"
53543
53528
 
53544
53529
  CMD="${"${SSH_ORIGINAL_COMMAND:-}"}"
53545
53530
  if [ -z "$CMD" ]; then
@@ -53548,48 +53533,11 @@ else
53548
53533
  SHELL_CMD=(bash -c "$CMD")
53549
53534
  fi
53550
53535
 
53551
- case "$(uname -s)" in
53552
- Darwin)
53553
- POLICY="(version 1)
53554
- (deny default)
53555
- (allow process*)
53556
- (allow signal (target self))
53557
- (allow sysctl-read)
53558
- (allow mach-lookup)
53559
- (allow file-read-metadata)
53560
- (allow network*)
53561
- (allow file-read* (subpath \"/usr\"))
53562
- (allow file-read* (subpath \"/bin\"))
53563
- (allow file-read* (subpath \"/sbin\"))
53564
- (allow file-read* (subpath \"/System\"))
53565
- (allow file-read* (subpath \"/Library\"))
53566
- (allow file-read* (subpath \"/etc\"))
53567
- (allow file-read* (subpath \"/private/etc\"))"
53568
- while IFS= read -r d; do
53569
- POLICY+="
53570
- (allow file-read* file-write* (subpath \"$d\"))"
53571
- done <<< "$EXPOSED_JSON"
53572
- exec sandbox-exec -p "$POLICY" "${"${SHELL_CMD[@]}"}"
53573
- ;;
53574
- Linux)
53575
- BWRAP_ARGS=(
53576
- --unshare-user --unshare-ipc --unshare-pid --unshare-uts
53577
- --die-with-parent
53578
- --proc /proc --dev /dev --tmpfs /tmp
53579
- --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /sbin /sbin
53580
- --ro-bind /lib /lib --ro-bind /etc /etc
53581
- )
53582
- if [ -d /lib64 ]; then BWRAP_ARGS+=(--ro-bind /lib64 /lib64); fi
53583
- while IFS= read -r d; do
53584
- BWRAP_ARGS+=(--bind "$d" "$d")
53585
- done <<< "$EXPOSED_JSON"
53586
- exec bwrap "${"${BWRAP_ARGS[@]}"}" "${"${SHELL_CMD[@]}"}"
53587
- ;;
53588
- *)
53589
- echo "clawd-ssh-jail: unsupported OS $(uname -s)" >&2
53590
- exit 1
53591
- ;;
53592
- esac
53536
+ # 去掉 OS 沙箱层(原本 macOS Seatbelt 策略 / Linux 命名空间隔离):SSH 进来即该用户
53537
+ # 权限内的 shell,直接 exec。沙箱只限制文件、拦不住网络/进程,隔离价值有限,且相关 API
53538
+ # 在 macOS 已 deprecated。授权即完全信任——sshAllowed 授权开关 + authorized_keys 即时
53539
+ # 踢行是唯一访问闸。要加回沙箱:从 git 历史(T-29 之前)恢复本段的 OS 分支即可。
53540
+ exec "${"${SHELL_CMD[@]}"}"
53593
53541
  `;
53594
53542
  function ensureJailScript(dataDir) {
53595
53543
  const binDir = import_node_path37.default.join(dataDir, "bin");
@@ -53636,6 +53584,8 @@ var SshdManager = class {
53636
53584
  stopping = false;
53637
53585
  exitHookInstalled = false;
53638
53586
  startupTimeoutMs;
53587
+ // sshd stderr 按行嗌探 pubkey 认证结果时的半行缓冲(防关键字被 chunk 边界切断)
53588
+ sniffBuf = "";
53639
53589
  get port() {
53640
53590
  return this.deps.port;
53641
53591
  }
@@ -53683,7 +53633,11 @@ var SshdManager = class {
53683
53633
  logStream.write(String(c));
53684
53634
  };
53685
53635
  proc.stdout?.on("data", tee);
53686
- proc.stderr?.on("data", tee);
53636
+ proc.stderr?.on("data", (c) => {
53637
+ const s = String(c);
53638
+ logStream.write(s);
53639
+ this.sniffSshdAuth(s);
53640
+ });
53687
53641
  proc.once("exit", () => logStream.end());
53688
53642
  const ready = await waitForSshdReady(proc, this.startupTimeoutMs);
53689
53643
  if (!ready.ok) {
@@ -53770,7 +53724,36 @@ ${tail}` : ready.error;
53770
53724
  } catch {
53771
53725
  }
53772
53726
  }
53727
+ // 把 sshd -e 输出里的 pubkey 认证结果转写一条到 contact-ssh.log,让 A 侧审计日志一处即可
53728
+ // 看到"隧道连上但 sshd 拒了 key"(否则只有 tunnel.connected + tunnel.disconnected,为什么
53729
+ // 被拒要翻另一个 sshd.log 对时间线)。按 \n 切行嗅探,半行留 sniffBuf 等下个 chunk 拼回。
53730
+ sniffSshdAuth(chunk) {
53731
+ const sshLog = this.deps.sshLog;
53732
+ if (!sshLog) return;
53733
+ this.sniffBuf += chunk;
53734
+ const parts = this.sniffBuf.split("\n");
53735
+ this.sniffBuf = parts.pop() ?? "";
53736
+ for (const line of parts) {
53737
+ if (/Accepted publickey/i.test(line)) {
53738
+ sshLog.info(
53739
+ "ssh.auth-ok",
53740
+ "sshd \u63A5\u53D7\u4E86 pubkey \u8BA4\u8BC1\uFF08\u5BF9\u7AEF key \u547D\u4E2D authorized_keys\uFF09",
53741
+ sshdAuthMeta(line)
53742
+ );
53743
+ } else if (/Failed publickey/i.test(line) || /Connection closed by (authenticating|invalid) user/i.test(line)) {
53744
+ sshLog.warn(
53745
+ "ssh.auth-rejected",
53746
+ "sshd \u62D2\u7EDD\u4E86 pubkey \u8BA4\u8BC1\uFF08\u5BF9\u7AEF key \u4E0D\u5728 authorized_keys / \u672A\u6388\u6743 / key \u672A\u62C9\u5230\uFF09",
53747
+ sshdAuthMeta(line)
53748
+ );
53749
+ }
53750
+ }
53751
+ }
53773
53752
  };
53753
+ function sshdAuthMeta(line) {
53754
+ const m2 = /port (\d+)/.exec(line);
53755
+ return m2 ? { clientPort: Number(m2[1]) } : {};
53756
+ }
53774
53757
  async function waitForSshdReady(proc, timeoutMs) {
53775
53758
  return new Promise((resolve6) => {
53776
53759
  let settled = false;
@@ -54161,6 +54144,11 @@ async function handleSshTunnelLocalUpgrade(req, socket, head, deps) {
54161
54144
  url: outboundUrl
54162
54145
  });
54163
54146
  deps.wss.handleUpgrade(req, socket, head, (clientWs) => {
54147
+ sshLog.info(
54148
+ "relay.client-connected",
54149
+ "relay CLI \u5DF2\u8FDE\u4E0A\u672C\u5730 daemon\uFF08client WS \u5347\u7EA7\u6210\u529F\uFF09\uFF0C\u5F00\u59CB\u62E8 peer",
54150
+ { peerDeviceId, peerDisplayName: contact.displayName }
54151
+ );
54164
54152
  const makeOut = deps.makeOutboundWs ?? ((u, o) => new import_websocket.default(u, o));
54165
54153
  const outbound = makeOut(outboundUrl, {
54166
54154
  headers: { Authorization: `Bearer ${contact.connectToken}` }
@@ -60620,7 +60608,9 @@ ${bar}
60620
60608
  port: config.sshdPort,
60621
60609
  logger,
60622
60610
  installProcessExitHandlers: true,
60623
- onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info)
60611
+ onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info),
60612
+ // sshd 的 pubkey 认证结果转写进 contact-ssh.log(补 A 侧「隧道连上但 sshd 拒了 key」盲区)
60613
+ sshLog
60624
60614
  });
60625
60615
  try {
60626
60616
  await sshdMgr.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.232",
3
+ "version": "0.2.233",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",