myagent-ai 1.31.3 → 1.32.2
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/core/browser_profile.py +7 -1
- package/core/web_control.py +34 -10
- package/core/workflow_engine.py +1002 -0
- package/package.json +1 -1
- package/web/api_server.py +313 -51
- package/web/ui/admin/admin-core.js +11 -3
- package/web/ui/admin/admin-sites.js +26 -8
- package/web/ui/admin/admin-skills.js +17 -4
- package/web/ui/admin/admin-workflows.js +621 -0
- package/web/ui/index.html +2 -0
- package/worklog.md +14 -415
package/core/browser_profile.py
CHANGED
|
@@ -99,9 +99,15 @@ class BrowserProfile:
|
|
|
99
99
|
base_dir: Profile 根目录
|
|
100
100
|
config: 预置配置(来自 PRESET_PROFILES)
|
|
101
101
|
"""
|
|
102
|
+
# [v1.32.2] 安全修复: 路径遍历防护
|
|
103
|
+
if ".." in name or "/" in name or "\\" in name or not name:
|
|
104
|
+
raise ValueError(f"无效的 Profile 名称: {name}")
|
|
102
105
|
self.name = name
|
|
103
106
|
self.base_dir = base_dir
|
|
104
|
-
|
|
107
|
+
# 二次校验: 确保解析后的路径仍在 base_dir 内
|
|
108
|
+
self.profile_dir = (base_dir / name).resolve()
|
|
109
|
+
if not str(self.profile_dir).startswith(str(base_dir.resolve())):
|
|
110
|
+
raise ValueError(f"Profile 名称 '{name}' 导致路径逃逸: {self.profile_dir}")
|
|
105
111
|
self.user_data_dir = self.profile_dir / "user_data"
|
|
106
112
|
self.cookies_file = self.profile_dir / "cookies.json"
|
|
107
113
|
self.config_file = self.profile_dir / "config.json"
|
package/core/web_control.py
CHANGED
|
@@ -713,17 +713,41 @@ class WebControlManager:
|
|
|
713
713
|
if parsed.scheme not in ('http', 'https'):
|
|
714
714
|
raise ValueError(f"Unsupported URL scheme: {parsed.scheme}")
|
|
715
715
|
|
|
716
|
-
#
|
|
716
|
+
# 禁止访问内网地址(防止 SSRF 攻击)
|
|
717
717
|
hostname = parsed.hostname
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
hostname
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
718
|
+
import ipaddress
|
|
719
|
+
if hostname:
|
|
720
|
+
# 解析 hostname 为 IP(处理 DNS Rebinding)
|
|
721
|
+
try:
|
|
722
|
+
import socket
|
|
723
|
+
resolved_ips = socket.getaddrinfo(hostname, parsed.port or 443, socket.AF_UNSPEC, socket.SOCK_STREAM)
|
|
724
|
+
for family, _, _, _, sockaddr in resolved_ips:
|
|
725
|
+
ip = sockaddr[0]
|
|
726
|
+
try:
|
|
727
|
+
ip_obj = ipaddress.ip_address(ip)
|
|
728
|
+
# 阻止所有内网地址
|
|
729
|
+
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_reserved or ip_obj.is_multicast:
|
|
730
|
+
raise ValueError(f"SSRF blocked: {hostname} resolves to private/reserved IP {ip}")
|
|
731
|
+
except ValueError:
|
|
732
|
+
raise
|
|
733
|
+
except socket.gaierror:
|
|
734
|
+
pass
|
|
735
|
+
# 字符串级别的快速检查(防止绕过)
|
|
736
|
+
blocked = (
|
|
737
|
+
hostname in ('localhost', '127.0.0.1', '0.0.0.0', '::1', '::') or
|
|
738
|
+
hostname.endswith('.local') or
|
|
739
|
+
hostname.endswith('.internal') or
|
|
740
|
+
hostname.startswith('10.') or
|
|
741
|
+
hostname.startswith('192.168.') or
|
|
742
|
+
# 172.16.0.0 - 172.31.255.255
|
|
743
|
+
(hostname.startswith('172.') and len(hostname.split('.')) >= 3 and 16 <= int(hostname.split('.')[1]) <= 31) or
|
|
744
|
+
# 169.254.0.0/16 云元数据
|
|
745
|
+
hostname.startswith('169.254.') or
|
|
746
|
+
# 100.64.0.0/10 CGN
|
|
747
|
+
hostname.startswith('100.') and len(hostname.split('.')) >= 2 and 64 <= int(hostname.split('.')[1]) <= 127
|
|
748
|
+
)
|
|
749
|
+
if blocked:
|
|
750
|
+
raise ValueError(f"SSRF blocked: cannot access internal address {hostname}")
|
|
727
751
|
|
|
728
752
|
# 检查缓存(仅对非 session 请求)
|
|
729
753
|
cache_key = hashlib.md5(url.encode()).hexdigest()
|