Files
op-packages/luci-app-openclaw/tests/test_device_pairing_contract.sh
T

110 lines
5.9 KiB
Bash

#!/bin/sh
# OpenClaw 浏览器设备配对语义与安全契约测试。
#
# 背景 (OpenClaw 2026.9.1+):
# 上游安全策略要求首次连接 Control UI 时进行设备配对审批:
# - 列出请求: openclaw devices list --json
# - 批准请求: openclaw devices approve <requestId>
#
# 核心规范:
# 1. 终端交互菜单 (oc-config-interactive.js) 与传统 Shell 菜单 (oc-config.sh)
# 必须提供设备配对管理与一键批准功能。
# 2. Web 控制台 (console.htm) 与终端配置 (advanced.htm) 必须提供一键批准设备配对能力,
# 且必须显式标注安全风险提示。
# 3. LuCI 控制器 (openclaw.lua) 的批准端点必须使用 post() 注册以防 CSRF 越权。
# 4. 所有受影响文件必须保持 LF 换行。
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
JS_CONFIG="$REPO_ROOT/root/usr/share/openclaw/oc-config-interactive.js"
SH_CONFIG="$REPO_ROOT/root/usr/share/openclaw/oc-config.sh"
CONTROLLER="$REPO_ROOT/luasrc/controller/openclaw.lua"
CONSOLE="$REPO_ROOT/luasrc/view/openclaw/console.htm"
ADVANCED="$REPO_ROOT/luasrc/view/openclaw/advanced.htm"
fail() {
echo "FAIL: $1" >&2
exit 1
}
for f in "$JS_CONFIG" "$SH_CONFIG" "$CONTROLLER" "$CONSOLE" "$ADVANCED"; do
[ -f "$f" ] || fail "missing $f"
done
# ── 1. 终端菜单 CLI 调用契约 ──
grep -Fq "'devices', 'list'" "$JS_CONFIG" || fail "interactive config must use devices list"
grep -Fq "'devices', 'approve'" "$JS_CONFIG" || fail "interactive config must use devices approve"
grep -Fq "oc_cmd devices list" "$SH_CONFIG" || fail "shell config must use oc_cmd devices list"
grep -Fq "oc_cmd devices approve" "$SH_CONFIG" || fail "shell config must use oc_cmd devices approve"
# ── 2. 菜单项注册与可达性 ──
grep -Fq "value: 'devices'" "$JS_CONFIG" || fail "interactive config must expose devices menu item"
grep -Fq "manageDevicePairing" "$JS_CONFIG" || fail "interactive config must implement manageDevicePairing"
grep -Fq "devices_pairing_menu" "$SH_CONFIG" || fail "shell config must implement devices_pairing_menu"
# ── 3. LuCI 控制器端点契约 (必须遵循 CSRF 安全规范) ──
grep -Fq 'entry({"admin", "services", "openclaw", "devices_list"}, call("action_devices_list"), nil).leaf = true' "$CONTROLLER" \
|| fail "controller must register devices_list with call()"
grep -Fq 'entry({"admin", "services", "openclaw", "devices_approve"}, post("action_devices_approve"), nil).leaf = true' "$CONTROLLER" \
|| fail "controller must register devices_approve with post()"
grep -Fq 'devices list --json' "$CONTROLLER" || fail "controller must invoke devices list --json"
grep -Fq 'devices approve' "$CONTROLLER" || fail "controller must invoke devices approve"
grep -Fq 'fix_openclaw_state_permissions' "$CONTROLLER" || fail "controller must fix permissions after approval"
# ── 4. Web 端安全风险提示契约 ──
# 必须在 4 处界面中显著标注风险说明
for f in "$JS_CONFIG" "$SH_CONFIG" "$CONSOLE" "$ADVANCED"; do
grep -Fq "风险提示" "$f" || fail "$(basename "$f") missing risk warning heading"
grep -Fq "完全控制权限" "$f" || fail "$(basename "$f") missing full control permission warning"
done
# ── 5. 前端 POST + CSRF 调用契约 ──
for f in "$CONSOLE" "$ADVANCED"; do
grep -Fq "devices_approve" "$f" || fail "$(basename "$f") missing devices_approve call"
grep -Fq "devices_list" "$f" || fail "$(basename "$f") missing devices_list call"
grep -Fq "ocCsrfToken" "$f" || fail "$(basename "$f") must send CSRF token"
done
# ── 6. XSS 防护契约 ──
for f in "$CONSOLE" "$ADVANCED"; do
grep -Fq "function escapeHtml" "$f" || fail "$(basename "$f") must define escapeHtml"
grep -Fq "escapeHtml(item.remoteIp" "$f" || fail "$(basename "$f") must escape remoteIp"
grep -Fq "escapeHtml(item.clientId" "$f" || fail "$(basename "$f") must escape clientId"
grep -Fq "escapeHtml(item.requestId" "$f" || fail "$(basename "$f") must escape requestId"
done
# ── 7. 控制器退出码与错误分支解析契约 ──
grep -Fq "__EXIT" "$CONTROLLER" || fail "controller must check command exit code"
grep -Fq "缺少 request_id 或 all 参数" "$CONTROLLER" || fail "controller must reject empty approve requests"
grep -Fq "if not output or exit_code ~= 0 then" "$CONTROLLER" || fail "controller must reject non-zero exit_code in devices_list"
grep -Fq "load_devices_list(json)" "$CONTROLLER" || fail "controller must use the shared devices list parser"
grep -Fq "oc_devices.evaluate_approval" "$CONTROLLER" || fail "controller must verify approval results"
grep -Fq "http.write(json.stringify(response))" "$CONTROLLER" || fail "controller must preserve JSON array markers"
grep -Fq "pending = oc_devices.json_array" "$CONTROLLER" || fail "controller must encode empty pending lists as arrays"
grep -Fq "OPENCLAW_SUPERVISOR_MODE=external" "$CONTROLLER" || fail "devices CLI must use external supervisor mode"
grep -Fq "timeout 20" "$CONTROLLER" || fail "devices CLI must have a timeout"
if grep -Fq 'out:lower():match("approved")' "$CONTROLLER"; then
fail "approval output text must never override a non-zero exit status"
fi
# ── 8. 前端状态与批准语义契约 ──
for f in "$CONSOLE" "$ADVANCED"; do
grep -Fq "success_count" "$f" || fail "$(basename "$f") must check success_count to prevent false reload on empty list"
grep -Fq "data.status !==" "$f" || fail "$(basename "$f") must check data.status before consuming devices list"
grep -Fq "res.status === 'partial'" "$f" || fail "$(basename "$f") must show partial/unconfirmed results"
if grep -Eq 'PairingMsg\.innerHTML|pairingMsg\.innerHTML' "$f"; then
fail "$(basename "$f") must not render CLI messages with innerHTML"
fi
done
# ── 8. 换行符约束 (LF only) ──
cr=$(printf '\r')
for f in "$JS_CONFIG" "$SH_CONFIG" "$CONTROLLER" "$CONSOLE" "$ADVANCED"; do
if LC_ALL=C grep -q "$cr" "$f"; then
fail "$(basename "$f") must use LF line endings"
fi
done
echo "ok"