From cf76f8556ab453a7648e4564abc1716c3e698de8 Mon Sep 17 00:00:00 2001 From: ringhyacinth <63448697+ringhyacinth@users.noreply.github.com> Date: Tue, 3 Mar 2026 07:44:48 +0800 Subject: [PATCH] Revert "fix: resolve hardcoded paths, port mismatch, XSS, and UX issues" --- README.md | 8 +- SKILL.md | 4 +- backend/app.py | 183 +++++----------------------------- convert_to_webp.py | 3 +- frontend/index.html | 4 +- frontend/join-office-skill.md | 5 +- healthcheck.sh | 8 +- office-agent-push.py | 7 +- repack_star_working.py | 2 +- set_state.py | 3 +- 10 files changed, 48 insertions(+), 179 deletions(-) diff --git a/README.md b/README.md index 8e59622..a48d91a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ cd backend python3 app.py ``` -打开:**http://127.0.0.1:19000** +打开:**http://127.0.0.1:18791** 切状态试试(在项目根目录执行): ```bash @@ -105,7 +105,7 @@ cd backend python3 app.py ``` -打开:`http://127.0.0.1:19000` +打开:`http://127.0.0.1:18791` ### 4) 切换主 Agent 状态(示例) @@ -254,7 +254,7 @@ cd backend python3 app.py ``` -Open: **http://127.0.0.1:19000** +Open: **http://127.0.0.1:18791** Try changing states (run from project root): ```bash @@ -328,7 +328,7 @@ cd backend python3 app.py ``` -Open: `http://127.0.0.1:19000` +Open: `http://127.0.0.1:18791` ### 4) Switch main Agent status (example) diff --git a/SKILL.md b/SKILL.md index 2258059..782e63e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -39,7 +39,7 @@ python3 app.py ``` 然后告诉主人: -> 好了,你现在打开 http://127.0.0.1:19000 就能看到像素办公室了! +> 好了,你现在打开 http://127.0.0.1:18791 就能看到像素办公室了! --- @@ -70,7 +70,7 @@ python3 set_state.py idle "待命中,随时准备为你服务" 如果你这台机器有 `cloudflared`,直接跑: ```bash -cloudflared tunnel --url http://127.0.0.1:19000 +cloudflared tunnel --url http://127.0.0.1:18791 ``` 会得到一个 `https://xxx.trycloudflare.com` 链接,发给主人即可。 diff --git a/backend/app.py b/backend/app.py index 82d0bce..3a79e84 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3,14 +3,10 @@ from flask import Flask, jsonify, send_from_directory, make_response, request from datetime import datetime, timedelta -from urllib.parse import urlparse -import hmac -import html as html_mod import json import os import re import threading -import uuid # Paths (project-relative, no hardcoded absolute paths) ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -20,12 +16,6 @@ STATE_FILE = os.path.join(ROOT_DIR, "state.json") AGENTS_STATE_FILE = os.path.join(ROOT_DIR, "agents-state.json") JOIN_KEYS_FILE = os.path.join(ROOT_DIR, "join-keys.json") -# Security/validation knobs -OFFICE_SET_STATE_TOKEN = (os.environ.get("OFFICE_SET_STATE_TOKEN") or os.environ.get("OFFICE_STATUS_SYNC_TOKEN") or "").strip() -MAX_DETAIL_LEN = int(os.environ.get("OFFICE_MAX_DETAIL_LEN", "200")) -MAX_NAME_LEN = int(os.environ.get("OFFICE_MAX_NAME_LEN", "50")) -CONTROL_CHAR_RE = re.compile(r"[\x00-\x1f\x7f]") - def get_yesterday_date_str(): """获取昨天的日期字符串 YYYY-MM-DD""" @@ -33,92 +23,25 @@ def get_yesterday_date_str(): return yesterday.strftime("%Y-%m-%d") -def clean_text(value, max_len=200): - """输入清洗:去控制字符、去首尾空格、限长、转义HTML实体。""" - if value is None: - return "" - text = str(value) - text = CONTROL_CHAR_RE.sub("", text).strip() - text = html_mod.escape(text) - if len(text) > max_len: - text = text[:max_len] - return text - - -def get_trace_id(): - return (request.headers.get("X-Trace-Id") or "").strip() or f"office-{uuid.uuid4().hex[:10]}" - - -def log_event(level, message, **fields): - payload = { - "ts": datetime.now().isoformat(), - "level": level, - "message": message, - } - payload.update(fields) - try: - print(json.dumps(payload, ensure_ascii=False), flush=True) - except Exception: - print(f"[{level}] {message} {fields}", flush=True) - - -def is_same_origin_request(): - """Allow browser same-origin requests (e.g. local control panel buttons).""" - req_host = (request.host or "").lower() - origin = request.headers.get("Origin") or request.headers.get("Referer") - if not origin: - return False - try: - parsed = urlparse(origin) - return (parsed.netloc or "").lower() == req_host - except Exception: - return False - - -def is_loopback_remote(): - addr = (request.remote_addr or "").strip() - return addr in {"127.0.0.1", "::1", "localhost"} - - -def is_set_state_authorized(): - """Authorize set_state: same-origin browser OR token OR local loopback fallback.""" - if is_same_origin_request(): - return True, "same-origin" - - token = (request.headers.get("X-Office-Token") or "").strip() - if OFFICE_SET_STATE_TOKEN and token and hmac.compare_digest(token, OFFICE_SET_STATE_TOKEN): - return True, "token" - - # Backward compatibility: keep local loopback calls working even if token is unset. - if not OFFICE_SET_STATE_TOKEN and is_loopback_remote(): - return True, "loopback-no-token" - - return False, "unauthorized" - - def sanitize_content(text): """清理内容,保护隐私""" import re - + # 移除 OpenID、User ID 等 text = re.sub(r'ou_[a-f0-9]+', '[用户]', text) text = re.sub(r'user_id="[^"]+"', 'user_id="[隐藏]"', text) - + + # 移除具体的人名(如果有的话) + # 这里可以根据需要添加更多规则 + # 移除 IP 地址、路径等敏感信息 text = re.sub(r'/root/[^"\s]+', '[路径]', text) text = re.sub(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', '[IP]', text) - - # 移除邮箱 + + # 移除电话号码、邮箱等 text = re.sub(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', '[邮箱]', text) - - # 补充敏感信息类型(身份证 / 银行卡 / JWT) - text = re.sub(r'\b\d{17}[\dXx]\b', '[身份证]', text) - text = re.sub(r'\b\d{16,19}\b', '[银行卡]', text) - text = re.sub(r'eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+', '[JWT]', text) - - # 最后处理手机号,避免误伤身份证串 - text = re.sub(r'\b1[3-9]\d{9}\b', '[手机号]', text) - + text = re.sub(r'1[3-9]\d{9}', '[手机号]', text) + return text @@ -164,10 +87,8 @@ def extract_memo_from_file(file_path): "「纸上得来终觉浅,绝知此事要躬行。」" ] - # Use date-based index so the same quote shows all day (no jitter on poll) - today = datetime.now().strftime("%Y%m%d") - quote_index = int(today) % len(wisdom_quotes) - quote = wisdom_quotes[quote_index] + import random + quote = random.choice(wisdom_quotes) # 组合内容 result = [] @@ -288,27 +209,9 @@ def load_state(): def save_state(state: dict): - """Save state to file and sync main agent in agents-state.json""" + """Save state to file""" with open(STATE_FILE, "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) - # Keep agents-state.json in sync for the main agent - _sync_main_agent_state(state) - - -def _sync_main_agent_state(state: dict): - """Update the isMain=true entry in agents-state.json to match state.json""" - try: - agents = load_agents_state() - for a in agents: - if a.get("isMain"): - a["state"] = state.get("state", "idle") - a["detail"] = state.get("detail", "") - a["updated_at"] = state.get("updated_at", datetime.now().isoformat()) - a["area"] = state_to_area(a["state"]) - break - save_agents_state(agents) - except Exception: - pass # Initialize state @@ -462,7 +365,6 @@ def get_agents(): cleaned_agents = [] keys_data = load_join_keys() - dirty = False # only write to disk if something actually changed for a in agents: if a.get("isMain"): @@ -485,7 +387,6 @@ def get_agents(): key_item["usedBy"] = None key_item["usedByAgentId"] = None key_item["usedAt"] = None - dirty = True continue except Exception: pass @@ -498,15 +399,13 @@ def get_agents(): age = (now - last_push_at).total_seconds() if age > 300: # 5分钟无推送自动离线 a["authStatus"] = "offline" - dirty = True except Exception: pass cleaned_agents.append(a) - if dirty: - save_agents_state(cleaned_agents) - save_join_keys(keys_data) + save_agents_state(cleaned_agents) + save_join_keys(keys_data) return jsonify(cleaned_agents) @@ -581,10 +480,10 @@ def join_agent(): if not isinstance(data, dict) or not data.get("name"): return jsonify({"ok": False, "msg": "请提供名字"}), 400 - name = clean_text(data["name"], MAX_NAME_LEN) + name = data["name"].strip() state = data.get("state", "idle") - detail = clean_text(data.get("detail", ""), MAX_DETAIL_LEN) - join_key = clean_text(data.get("joinKey", ""), 128) + detail = data.get("detail", "") + join_key = data.get("joinKey", "").strip() # Normalize state early for compatibility state = normalize_agent_state(state) @@ -780,16 +679,16 @@ def agent_push(): if not isinstance(data, dict): return jsonify({"ok": False, "msg": "invalid json"}), 400 - trace_id = get_trace_id() - agent_id = clean_text(data.get("agentId"), 128) - join_key = clean_text(data.get("joinKey"), 128) - state = clean_text(data.get("state"), 32) - detail = clean_text(data.get("detail"), MAX_DETAIL_LEN) - name = clean_text(data.get("name"), MAX_NAME_LEN) + agent_id = (data.get("agentId") or "").strip() + join_key = (data.get("joinKey") or "").strip() + state = (data.get("state") or "").strip() + detail = (data.get("detail") or "").strip() + name = (data.get("name") or "").strip() if not agent_id or not join_key or not state: return jsonify({"ok": False, "msg": "缺少 agentId/joinKey/state"}), 400 + valid_states = {"idle", "writing", "researching", "executing", "syncing", "error"} state = normalize_agent_state(state) keys_data = load_join_keys() @@ -828,17 +727,8 @@ def agent_push(): target["lastPushAt"] = datetime.now().isoformat() save_agents_state(agents) - log_event( - "info", - "agent_push_ok", - traceId=trace_id, - agentId=agent_id, - state=state, - source="remote-openclaw", - ) return jsonify({"ok": True, "agentId": agent_id, "area": target.get("area")}) except Exception as e: - log_event("error", "agent_push_failed", error=str(e)) return jsonify({"ok": False, "msg": str(e)}), 500 @@ -897,40 +787,22 @@ def get_yesterday_memo(): @app.route("/set_state", methods=["POST"]) def set_state_endpoint(): """Set state via POST (for UI control panel)""" - trace_id = get_trace_id() try: - ok, reason = is_set_state_authorized() - if not ok: - log_event("warn", "set_state_denied", traceId=trace_id, reason=reason, remote=request.remote_addr) - return jsonify({"status": "error", "msg": "unauthorized"}), 401 - data = request.get_json() if not isinstance(data, dict): return jsonify({"status": "error", "msg": "invalid json"}), 400 - state = load_state() if "state" in data: - s = normalize_agent_state(data["state"]) + s = data["state"] valid_states = {"idle", "writing", "researching", "executing", "syncing", "error"} if s in valid_states: state["state"] = s if "detail" in data: - state["detail"] = clean_text(data["detail"], MAX_DETAIL_LEN) - + state["detail"] = data["detail"] state["updated_at"] = datetime.now().isoformat() save_state(state) - - log_event( - "info", - "set_state_ok", - traceId=trace_id, - auth=reason, - state=state.get("state"), - remote=request.remote_addr, - ) return jsonify({"status": "ok"}) except Exception as e: - log_event("error", "set_state_failed", traceId=trace_id, error=str(e)) return jsonify({"status": "error", "msg": str(e)}), 500 @@ -938,9 +810,8 @@ if __name__ == "__main__": print("=" * 50) print("Star Office UI - Backend State Service") print("=" * 50) - OFFICE_PORT = int(os.environ.get("OFFICE_PORT", "19000")) print(f"State file: {STATE_FILE}") - print(f"Listening on: http://0.0.0.0:{OFFICE_PORT}") + print("Listening on: http://0.0.0.0:18791") print("=" * 50) - app.run(host="0.0.0.0", port=OFFICE_PORT, debug=False) + app.run(host="0.0.0.0", port=18791, debug=False) diff --git a/convert_to_webp.py b/convert_to_webp.py index 0cf31f4..396db86 100644 --- a/convert_to_webp.py +++ b/convert_to_webp.py @@ -9,8 +9,7 @@ import os from PIL import Image # 路径 -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -FRONTEND_DIR = os.path.join(ROOT_DIR, "frontend") +FRONTEND_DIR = "/root/.openclaw/workspace/star-office-ui/frontend" STATIC_DIR = os.path.join(FRONTEND_DIR, "") # 文件分类配置 diff --git a/frontend/index.html b/frontend/index.html index 911cb56..41d06cd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -508,7 +508,7 @@ if (data.success && data.memo) { memoDate.textContent = data.date || ''; - memoContent.textContent = data.memo; + memoContent.innerHTML = data.memo.replace(/\n/g, '
'); } else { memoContent.innerHTML = '
暂无昨日日记
'; } @@ -789,7 +789,7 @@ } list.innerHTML = visitors.map(agent => { - const name = (agent.name || '未命名访客').replace(/[<>"'&]/g, c => ({'<':'<','>':'>','"':'"',"'":''','&':'&'}[c])); + const name = agent.name || '未命名访客'; const authStatus = agent.authStatus || 'pending'; const state = agent.state || 'idle'; const statusMap = { diff --git a/frontend/join-office-skill.md b/frontend/join-office-skill.md index 7c4a37a..8ef9ed8 100644 --- a/frontend/join-office-skill.md +++ b/frontend/join-office-skill.md @@ -18,14 +18,15 @@ - 下载或复制 `office-agent-push.py`(可以访问:https://office.example.com/static/office-agent-push.py) - **最简单推荐**:直接运行脚本(已内置 state.json 自动发现) - 会自动尝试以下路径: - - `脚本同目录/state.json` + - `/root/.openclaw/workspace/star-office-ui/state.json` + - `/root/.openclaw/workspace/state.json` - `当前工作目录/state.json` - `脚本同目录/state.json` - 若你的环境路径特殊,再手动指定: - `OFFICE_LOCAL_STATE_FILE=/你的/state.json/路径` - 如果你不方便提供 state 文件,再用 /status 鉴权方式: - `OFFICE_LOCAL_STATUS_TOKEN=<你的token>` - - (可选)`OFFICE_LOCAL_STATUS_URL=http://127.0.0.1:19000/status` + - (可选)`OFFICE_LOCAL_STATUS_URL=http://127.0.0.1:18791/status` - 填入配置后运行 3. 脚本会自动: diff --git a/healthcheck.sh b/healthcheck.sh index ae20032..b5b2890 100755 --- a/healthcheck.sh +++ b/healthcheck.sh @@ -2,16 +2,14 @@ # Star Office UI Health Check # Checks if backend is responding, restarts if not -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -OFFICE_PORT="${OFFICE_PORT:-19000}" -BACKEND_URL="http://127.0.0.1:${OFFICE_PORT}/health" -LOG_FILE="${SCRIPT_DIR}/healthcheck.log" +BACKEND_URL="http://127.0.0.1:18791/health" +LOG_FILE="/root/.openclaw/workspace/star-office-ui/healthcheck.log" # Log timestamp echo "[$(date '+%Y-%m-%d %H:%M:%S')] Health check starting..." >> "$LOG_FILE" # Check backend -if curl -fsS --max-time 5 "$BACKEND_URL" > /dev/null 2>&1; then +if curl -sS "$BACKEND_URL" > /dev/null 2>&1; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backend is healthy" >> "$LOG_FILE" else echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backend is NOT healthy - restarting..." >> "$LOG_FILE" diff --git a/office-agent-push.py b/office-agent-push.py index ac8f905..7184623 100644 --- a/office-agent-push.py +++ b/office-agent-push.py @@ -31,15 +31,16 @@ STATE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "office-ag # 优先读取本机 OpenClaw 工作区的状态文件(更贴合 AGENTS.md 的工作流) # 支持自动发现,减少对方手动配置成本。 -_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) DEFAULT_STATE_CANDIDATES = [ - os.path.join(_SCRIPT_DIR, "state.json"), + "/root/.openclaw/workspace/star-office-ui/state.json", + "/root/.openclaw/workspace/state.json", os.path.join(os.getcwd(), "state.json"), + os.path.join(os.path.dirname(os.path.abspath(__file__)), "state.json"), ] # 如果对方本地 /status 需要鉴权,可在这里填写 token(或通过环境变量 OFFICE_LOCAL_STATUS_TOKEN) LOCAL_STATUS_TOKEN = os.environ.get("OFFICE_LOCAL_STATUS_TOKEN", "") -LOCAL_STATUS_URL = os.environ.get("OFFICE_LOCAL_STATUS_URL", "http://127.0.0.1:19000/status") +LOCAL_STATUS_URL = os.environ.get("OFFICE_LOCAL_STATUS_URL", "http://127.0.0.1:18791/status") # 可选:直接指定本地状态文件路径(最简单方案:绕过 /status 鉴权) LOCAL_STATE_FILE = os.environ.get("OFFICE_LOCAL_STATE_FILE", "") VERBOSE = os.environ.get("OFFICE_VERBOSE", "0") in {"1", "true", "TRUE", "yes", "YES"} diff --git a/repack_star_working.py b/repack_star_working.py index 26a8e5a..82f86f1 100644 --- a/repack_star_working.py +++ b/repack_star_working.py @@ -23,7 +23,7 @@ import math import os from PIL import Image -ROOT = os.path.dirname(os.path.abspath(__file__)) +ROOT = "/root/.openclaw/workspace/star-office-ui" IN_PATH = os.path.join(ROOT, "frontend", "star-working-spritesheet.png") OUT_PATH = os.path.join(ROOT, "frontend", "star-working-spritesheet-grid.png") diff --git a/set_state.py b/set_state.py index 79096c5..3092b37 100644 --- a/set_state.py +++ b/set_state.py @@ -6,8 +6,7 @@ import os import sys from datetime import datetime -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -STATE_FILE = os.path.join(ROOT_DIR, "state.json") +STATE_FILE = "/root/.openclaw/workspace/star-office-ui/state.json" VALID_STATES = [ "idle",