Initial commit with translated description

This commit is contained in:
2026-03-29 13:14:42 +08:00
commit 0c8a4c3a37
15 changed files with 2951 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# SafeExec AI Wrapper - 上下文感知集成
# 用于 OpenClaw Agent 集成,自动传递用户消息上下文
SAFE_EXEC_BIN="$HOME/.local/bin/safe-exec"
# 从参数或环境变量获取上下文
if [[ $# -ge 2 ]]; then
# 命令行参数模式: safe-exec-ai "用户上下文" "命令"
USER_CONTEXT="$1"
COMMAND="$2"
shift 2
elif [[ -n "${SAFEXEC_CONTEXT:-}" ]]; then
# 环境变量模式
USER_CONTEXT="$SAFEXEC_CONTEXT"
COMMAND="$1"
shift
else
# 没有上下文,直接执行
exec $SAFE_EXEC_BIN "$@"
exit $?
fi
# 导出上下文环境变量
export SAFEXEC_CONTEXT="$USER_CONTEXT"
# 执行命令
exec $SAFE_EXEC_BIN "$COMMAND"

View File

@@ -0,0 +1,73 @@
#!/bin/bash
# safe-exec-approve - 批准待执行的命令
REQUEST_ID="$1"
SAFE_EXEC_DIR="$HOME/.openclaw/safe-exec"
PENDING_DIR="$SAFE_EXEC_DIR/pending"
if [[ -z "$REQUEST_ID" ]]; then
echo "用法: safe-exec-approve <request_id>"
echo ""
echo "查看待处理的请求:"
echo " ls ~/.openclaw/safe-exec/pending/"
exit 1
fi
REQUEST_FILE="$PENDING_DIR/$REQUEST_ID.json"
if [[ ! -f "$REQUEST_FILE" ]]; then
echo "❌ 请求 $REQUEST_ID 不存在"
exit 1
fi
# 读取请求信息
COMMAND=$(jq -r '.command' "$REQUEST_FILE")
RISK=$(jq -r '.risk' "$REQUEST_FILE")
# 检测运行环境
IS_INTERACTIVE=false
if [[ -t 0 ]]; then
# 检查 stdin 是否是终端
IS_INTERACTIVE=true
fi
# 检查是否由 OpenClaw Agent 调用
if [[ -n "$OPENCLAW_AGENT_CALL" ]] || [[ -n "$SAFE_EXEC_AUTO_CONFIRM" ]]; then
IS_INTERACTIVE=false
fi
echo "⚠️ 即将执行以下命令:"
echo ""
echo "风险等级: ${RISK^^}"
echo "命令: $COMMAND"
echo ""
# 请求确认(仅在交互式环境)
if [[ "$IS_INTERACTIVE" == "true" ]]; then
read -p "确认执行?(yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
echo "❌ 已取消"
exit 0
fi
echo "✅ 已确认"
else
echo "🤖 非交互式环境 - 自动跳过确认"
fi
# 标记为已批准并执行
jq '.status = "approved"' "$REQUEST_FILE" > "$REQUEST_FILE.tmp" && mv "$REQUEST_FILE.tmp" "$REQUEST_FILE"
echo "✅ 执行中..."
eval "$COMMAND"
exit_code=$?
# 清理已处理的请求
rm "$REQUEST_FILE"
if [[ $exit_code -eq 0 ]]; then
echo "✅ 命令执行成功"
else
echo "⚠️ 命令执行失败(退出码: $exit_code"
fi
exit $exit_code

41
scripts/safe-exec-list.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# safe-exec-list - 列出所有待处理的批准请求
SAFE_EXEC_DIR="$HOME/.openclaw/safe-exec"
PENDING_DIR="$SAFE_EXEC_DIR/pending"
if [[ ! -d "$PENDING_DIR" ]]; then
echo "没有待处理的请求"
exit 0
fi
REQUESTS=("$PENDING_DIR"/*.json)
if [[ ! -e "${REQUESTS[0]}" ]]; then
echo "没有待处理的请求"
exit 0
fi
echo "📋 待处理的批准请求:"
echo ""
for request_file in "${REQUESTS[@]}"; do
if [[ -f "$request_file" ]]; then
request_id=$(basename "$request_file" .json)
command=$(jq -r '.command' "$request_file")
risk=$(jq -r '.risk' "$request_file")
reason=$(jq -r '.reason' "$request_file")
timestamp=$(jq -r '.timestamp' "$request_file")
time_str=$(date -d "@$timestamp" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "未知时间")
echo "📌 请求 ID: $request_id"
echo " 风险: ${risk^^}"
echo " 命令: $command"
echo " 原因: $reason"
echo " 时间: $time_str"
echo ""
fi
done
echo "批准命令: safe-exec-approve <request_id>"
echo "拒绝命令: safe-exec-reject <request_id>"

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# safe-exec-reject - 拒绝待执行的命令
REQUEST_ID="$1"
SAFE_EXEC_DIR="$HOME/.openclaw/safe-exec"
PENDING_DIR="$SAFE_EXEC_DIR/pending"
if [[ -z "$REQUEST_ID" ]]; then
echo "用法: safe-exec-reject <request_id>"
echo ""
echo "查看待处理的请求:"
echo " ls ~/.openclaw/safe-exec/pending/"
exit 1
fi
REQUEST_FILE="$PENDING_DIR/$REQUEST_ID.json"
if [[ ! -f "$REQUEST_FILE" ]]; then
echo "❌ 请求 $REQUEST_ID 不存在"
exit 1
fi
# 读取请求信息
COMMAND=$(jq -r '.command' "$REQUEST_FILE")
# 标记为已拒绝
jq '.status = "rejected"' "$REQUEST_FILE" > "$REQUEST_FILE.tmp" && mv "$REQUEST_FILE.tmp" "$REQUEST_FILE"
echo "❌ 命令已拒绝: $COMMAND"
# 记录到审计日志
AUDIT_LOG="$HOME/.openclaw/safe-exec-audit.log"
echo "{\"timestamp\":\"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\",\"event\":\"rejected\",\"requestId\":\"$REQUEST_ID\",\"command\":\"$(echo "$COMMAND" | jq -Rs .)\"}" >> "$AUDIT_LOG"
# 清理已处理的请求
rm "$REQUEST_FILE"
echo "请求已拒绝并清理"

404
scripts/safe-exec.sh Normal file
View File

@@ -0,0 +1,404 @@
#!/bin/bash
# SafeExec v0.3.3 - 安全增强版本
# 移除监控组件,添加完整的 metadata 声明
SAFE_EXEC_DIR="$HOME/.openclaw/safe-exec"
AUDIT_LOG="$HOME/.openclaw/safe-exec-audit.log"
PENDING_DIR="$SAFE_EXEC_DIR/pending"
RULES_FILE="$HOME/.openclaw/safe-exec-rules.json"
REQUEST_TIMEOUT=300 # 5分钟超时
# 上下文感知配置
USER_CONTEXT="${SAFEXEC_CONTEXT:-}"
mkdir -p "$PENDING_DIR"
# 检查 SafeExec 是否启用
is_enabled() {
if [[ ! -f "$RULES_FILE" ]]; then
echo "true"
return
fi
# 检查文件格式
local first_char=$(head -c 1 "$RULES_FILE")
if [[ "$first_char" == "[" ]]; then
# 数组格式,默认启用
echo "true"
return
fi
# 对象格式,检查 enabled 字段
local enabled
enabled=$(jq -r 'if .enabled == true then "true" else "false" end' "$RULES_FILE" 2>/dev/null)
# 如果解析失败,默认启用
if [[ -z "$enabled" ]] || [[ "$enabled" == "null" ]]; then
echo "true"
else
echo "$enabled"
fi
}
# 设置启用状态
set_enabled() {
local value="$1"
if [[ ! -f "$RULES_FILE" ]]; then
echo "{\"enabled\":$value,\"rules\":[]}" > "$RULES_FILE"
else
# 检查文件格式(数组 vs 对象)
local first_char=$(head -c 1 "$RULES_FILE")
if [[ "$first_char" == "[" ]]; then
# 当前是数组格式,转换为对象格式
local rules_array=$(cat "$RULES_FILE")
echo "{\"enabled\":$value,\"rules\":$rules_array}" > "$RULES_FILE"
else
# 当前是对象格式,直接更新
jq ".enabled = $value" "$RULES_FILE" > "$RULES_FILE.tmp" && mv "$RULES_FILE.tmp" "$RULES_FILE"
fi
fi
local status
if [[ "$value" == "true" ]]; then
status="✅ 已启用"
else
status="❌ 已禁用"
fi
echo "$status"
log_audit "toggle" "{\"enabled\":$value}"
}
# 获取自定义确认关键词
get_confirmation_keywords() {
if [[ ! -f "$RULES_FILE" ]]; then
# 默认关键词
echo "我已明确风险|I understand the risk"
return
fi
# 检查文件格式
local first_char=$(head -c 1 "$RULES_FILE")
if [[ "$first_char" == "[" ]]; then
# 数组格式,返回默认关键词
echo "我已明确风险|I understand the risk"
return
fi
# 对象格式,尝试读取自定义关键词
local keywords
keywords=$(jq -r '.contextAware.confirmationKeywords // "我已明确风险|I understand the risk"' "$RULES_FILE" 2>/dev/null)
if [[ -z "$keywords" ]] || [[ "$keywords" == "null" ]]; then
echo "我已明确风险|I understand the risk"
else
echo "$keywords"
fi
}
# 检测用户明确确认
detect_user_confirmation() {
local context="$1"
local keywords=$(get_confirmation_keywords)
# 检查上下文中是否包含确认关键词
if echo "$context" | grep -qE "$keywords"; then
echo "confirmed"
return 0
fi
echo "normal"
return 1
}
# 显示当前状态
show_status() {
local enabled
enabled=$(is_enabled)
echo "🛡️ SafeExec 状态"
echo ""
if [[ "$enabled" == "true" ]]; then
echo "状态: ✅ **已启用**"
echo ""
echo "危险命令将被拦截并请求批准。"
else
echo "状态: ❌ **已禁用**"
echo ""
echo "⚠️ 所有命令将直接执行,不受保护!"
echo "建议仅在可信环境中禁用。"
fi
echo ""
echo "切换状态:"
echo " 启用: safe-exec --enable"
echo " 禁用: safe-exec --disable"
}
# 清理过期的请求
cleanup_expired_requests() {
local now=$(date +%s)
local count=0
for request_file in "$PENDING_DIR"/*.json; do
if [[ -f "$request_file" ]]; then
local timestamp=$(jq -r '.timestamp' "$request_file" 2>/dev/null)
if [[ -n "$timestamp" ]]; then
local age=$((now - timestamp))
if [[ $age -gt $REQUEST_TIMEOUT ]]; then
local request_id=$(basename "$request_file" .json)
jq '.status = "expired"' "$request_file" > "$request_file.tmp" && mv "$request_file.tmp" "$request_file"
echo "{\"timestamp\":\"$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")\",\"event\":\"expired\",\"requestId\":\"$request_id\",\"age\":$age}" >> "$AUDIT_LOG"
rm -f "$request_file"
count=$((count + 1))
fi
fi
fi
done
return $count
}
log_audit() {
local event="$1"
local data="$2"
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
# 构造完整的 JSON 对象
# 注意data 参数应该已经是 JSON 格式,但不带外层花括号
# 例如data='"requestId":"xxx","command":"xxx"'
# 我们需要移除 data 的外层花括号(如果有的话)
local clean_data="${data#{}" # 移除开头的 {
clean_data="${clean_data%\}}" # 移除结尾的 }
echo "{\"timestamp\":\"$timestamp\",\"event\":\"$event\",$clean_data}" >> "$AUDIT_LOG"
}
assess_risk() {
local cmd="$1"
local risk="low"
local reason=""
if [[ "$cmd" == *":(){:|:&};:"* ]] || [[ "$cmd" == *":(){ :|:& };:"* ]]; then
risk="critical"
reason="Fork炸弹"
elif echo "$cmd" | grep -qE 'rm[[:space:]]+-rf[[:space:]]+[\/~]'; then
risk="critical"
reason="删除根目录或家目录文件"
elif echo "$cmd" | grep -qE 'dd[[:space:]]+if='; then
risk="critical"
reason="磁盘破坏命令"
elif echo "$cmd" | grep -qE 'mkfs\.'; then
risk="critical"
reason="格式化文件系统"
elif echo "$cmd" | grep -qE '>[[:space:]]*/dev/sd[a-z]'; then
risk="critical"
reason="直接写入磁盘"
elif echo "$cmd" | grep -qE 'chmod[[:space:]]+777'; then
risk="high"
reason="设置文件为全局可写"
elif echo "$cmd" | grep -qE '>[[:space:]]*/(etc|boot|sys|root)/'; then
risk="high"
reason="写入系统目录"
elif echo "$cmd" | grep -qE '(curl|wget).*|[[:space:]]*(bash|sh|python)'; then
risk="high"
reason="管道下载到shell"
elif echo "$cmd" | grep -qE 'sudo[[:space:]]+'; then
risk="medium"
reason="使用特权执行"
elif echo "$cmd" | grep -qE 'iptables|firewall-cmd|ufw'; then
risk="medium"
reason="修改防火墙规则"
fi
echo "{\"risk\":\"$risk\",\"reason\":\"$reason\"}"
}
request_approval() {
local command="$1"
local risk="$2"
local reason="$3"
local request_id="req_$(date +%s)_$(shuf -i 1000-9999 -n 1)"
echo "{\"id\":\"$request_id\",\"command\":$(echo "$command" | jq -Rs .),\"risk\":\"$risk\",\"reason\":\"$reason\",\"timestamp\":$(date +%s),\"status\":\"pending\"}" > "$PENDING_DIR/$request_id.json"
log_audit "approval_requested" "{\"requestId\":\"$request_id\",\"command\":$(echo "$command" | jq -Rs .),\"risk\":\"$risk\",\"reason\":\"$reason\"}"
cat <<EOF
🚨 **危险操作检测 - 命令已拦截**
**风险等级:** ${risk^^}
**命令:** \`$command\`
**原因:** $reason
**请求 ID:** \`$request_id\`
此命令需要用户批准才能执行。
**批准方法:**
1. 在终端运行: \`safe-exec-approve $request_id\`
2. 或者: \`safe-exec-list\` 查看所有待处理请求
**拒绝方法:**
\`safe-exec-reject $request_id\`
⏰ 请求将在 5 分钟后过期
EOF
return 0
}
main() {
local command="$*"
if [[ -z "$command" ]]; then
echo "用法: safe-exec \"<命令>\""
exit 1
fi
# 检查是否启用
local enabled
enabled=$(is_enabled)
if [[ "$enabled" != "true" ]]; then
# SafeExec 已禁用,直接执行命令
log_audit "bypassed" "{\"command\":$(echo "$command" | jq -Rs .),\"reason\":\"SafeExec disabled\"}"
eval "$command"
exit $?
fi
# 自动清理过期请求
cleanup_expired_requests
local assessment
assessment=$(assess_risk "$command")
local risk
local reason
risk=$(echo "$assessment" | jq -r '.risk')
reason=$(echo "$assessment" | jq -r '.reason')
# ========== 上下文感知拦截 ==========
# 检查用户是否提供了明确的确认关键词
if [[ -n "$USER_CONTEXT" ]]; then
local confirmation
confirmation=$(detect_user_confirmation "$USER_CONTEXT")
if [[ "$confirmation" == "confirmed" ]]; then
# 用户已明确风险,根据原风险等级决定处理方式
if [[ "$risk" == "critical" ]]; then
# CRITICAL 风险:降级到 MEDIUM仍需批准但降低警告级别
echo "⚠️ 检测到确认关键词,但风险等级为 CRITICAL"
echo " 命令降级到 MEDIUM但仍需批准"
risk="medium"
log_audit "context_aware_downgrade" "{\"originalRisk\":\"critical\",\"newRisk\":\"medium\",\"reason\":\"用户确认关键词\",\"context\":$(echo "$USER_CONTEXT" | jq -Rs .)}"
elif [[ "$risk" == "high" ]]; then
# HIGH 风险:降级到 LOW直接执行
echo "✅ 检测到确认关键词,风险等级从 HIGH 降级到 LOW"
echo "⚡ 直接执行命令: $command"
log_audit "context_aware_allowed" "{\"originalRisk\":\"high\",\"newRisk\":\"low\",\"reason\":\"用户确认关键词\",\"context\":$(echo "$USER_CONTEXT" | jq -Rs .)}"
eval "$command"
exit $?
elif [[ "$risk" == "medium" ]]; then
# MEDIUM 风险:降级到 LOW直接执行
echo "✅ 检测到确认关键词,风险等级从 MEDIUM 降级到 LOW"
echo "⚡ 直接执行命令: $command"
log_audit "context_aware_allowed" "{\"originalRisk\":\"medium\",\"newRisk\":\"low\",\"reason\":\"用户确认关键词\",\"context\":$(echo "$USER_CONTEXT" | jq -Rs .)}"
eval "$command"
exit $?
fi
fi
fi
# ========== 上下文感知拦截结束 ==========
if [[ "$risk" == "low" ]]; then
log_audit "allowed" "{\"command\":$(echo "$command" | jq -Rs .),\"risk\":\"low\"}"
eval "$command"
exit $?
fi
request_approval "$command" "$risk" "$reason"
exit 0
}
# 处理命令行参数
case "$1" in
--enable)
set_enabled "true"
exit 0
;;
--disable)
set_enabled "false"
exit 0
;;
--status)
show_status
exit 0
;;
--approve)
request_file="$PENDING_DIR/$2.json"
if [[ -f "$request_file" ]]; then
command=$(jq -r '.command' "$request_file")
echo "✅ 执行命令: $command"
log_audit "executed" "{\"requestId\":\"$2\"}"
eval "$command"
exit_code=$?
rm -f "$request_file"
exit $exit_code
fi
echo "❌ 请求不存在: $2"
exit 1
;;
--reject)
request_file="$PENDING_DIR/$2.json"
if [[ -f "$request_file" ]]; then
command=$(jq -r '.command' "$request_file")
log_audit "rejected" "{\"requestId\":\"$2\"}"
rm -f "$request_file"
echo "❌ 请求已拒绝"
exit 0
fi
echo "❌ 请求不存在: $2"
exit 1
;;
--list)
echo "📋 **待处理的批准请求:**"
echo ""
count=0
for f in "$PENDING_DIR"/*.json; do
if [[ -f "$f" ]]; then
count=$((count + 1))
id=$(basename "$f" .json)
cmd=$(jq -r '.command' "$f")
rsk=$(jq -r '.risk' "$f")
reason=$(jq -r '.reason' "$f")
printf "📌 **请求 %d**\n" "$count"
printf " **ID:** \`%s\`\n" "$id"
printf " **风险:** %s\n" "${rsk^^}"
printf " **命令:** \`%s\`\n" "$cmd"
printf " **原因:** %s\n" "$reason"
echo ""
printf " 批准: \`safe-exec-approve %s\`\n" "$id"
printf " 拒绝: \`safe-exec-reject %s\`\n" "$id"
echo ""
fi
done
if [[ $count -eq 0 ]]; then
echo "✅ 没有待处理的请求"
fi
exit 0
;;
--cleanup)
cleanup_expired_requests
echo "✅ 清理完成"
exit 0
;;
esac
main "$@"