@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.
- package/dist/cli.cjs +65 -75
- 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 \
|
|
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
|
-
- \
|
|
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
|
|
53468
|
-
# 2.
|
|
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
|
-
|
|
53517
|
-
|
|
53518
|
-
|
|
53519
|
-
|
|
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
|
-
|
|
53552
|
-
|
|
53553
|
-
|
|
53554
|
-
|
|
53555
|
-
|
|
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",
|
|
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();
|