agent-moss 0.2.0__py3-none-any.whl
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.
- agent_moss/__init__.py +3 -0
- agent_moss/__version__.py +1 -0
- agent_moss/adapters/__init__.py +7 -0
- agent_moss/adapters/observable.py +104 -0
- agent_moss/cli.py +130 -0
- agent_moss/engine/__init__.py +18 -0
- agent_moss/engine/analyzer.py +288 -0
- agent_moss/engine/coordinator.py +261 -0
- agent_moss/engine/heuristic.py +213 -0
- agent_moss/engine/llm_analyzer.py +250 -0
- agent_moss/engine/logic_rules.py +219 -0
- agent_moss/engine/script_analyzer.py +347 -0
- agent_moss/engine/skill_engine.py +195 -0
- agent_moss/engine/types.py +48 -0
- agent_moss/infra/__init__.py +0 -0
- agent_moss/infra/config.json +31 -0
- agent_moss/infra/config.py +372 -0
- agent_moss/infra/llm_client.py +215 -0
- agent_moss/infra/logging.py +95 -0
- agent_moss/infra/parsers.py +249 -0
- agent_moss/infra/policy_cache.py +175 -0
- agent_moss/infra/prompt_templates.py +130 -0
- agent_moss/profiles/__init__.py +52 -0
- agent_moss/profiles/base.py +106 -0
- agent_moss/profiles/linux.py +339 -0
- agent_moss/profiles/windows.py +258 -0
- agent_moss/rules/user_rules.json +77 -0
- agent_moss/server/__init__.py +1 -0
- agent_moss/server/app.py +55 -0
- agent_moss/server/middleware.py +32 -0
- agent_moss/server/models.py +50 -0
- agent_moss/server/routes.py +83 -0
- agent_moss/server/socket_server.py +40 -0
- agent_moss/skills/browser_web_access_guard.md +31 -0
- agent_moss/skills/data_exfiltration_guard.md +33 -0
- agent_moss/skills/email_operation_guard.md +30 -0
- agent_moss/skills/file_access_guard.md +33 -0
- agent_moss/skills/general_tool_risk_guard.md +33 -0
- agent_moss/skills/intent_deviation_guard.md +39 -0
- agent_moss/skills/lateral_movement_guard.md +31 -0
- agent_moss/skills/persistence_backdoor_guard.md +32 -0
- agent_moss/skills/resource_exhaustion_guard.md +32 -0
- agent_moss/skills/script_execution_guard.md +37 -0
- agent_moss/skills/skill_installation_guard.md +30 -0
- agent_moss/skills/supply_chain_guard.md +31 -0
- agent_moss/templates/policy_mapping.md +87 -0
- agent_moss/templates/prompt1_template.txt +62 -0
- agent_moss/templates/prompt2_template.txt +45 -0
- agent_moss/templates/security_judge_template.txt +71 -0
- agent_moss-0.2.0.dist-info/METADATA +432 -0
- agent_moss-0.2.0.dist-info/RECORD +55 -0
- agent_moss-0.2.0.dist-info/WHEEL +5 -0
- agent_moss-0.2.0.dist-info/entry_points.txt +2 -0
- agent_moss-0.2.0.dist-info/licenses/LICENSE +21 -0
- agent_moss-0.2.0.dist-info/top_level.txt +1 -0
agent_moss/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Adapter for AgentOS observability service syscall data.
|
|
2
|
+
|
|
3
|
+
将 AgentOS 可观测服务采集的 syscall 数据转换为标准 AnalyzeRequest 格式。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ObservableAdapter:
|
|
12
|
+
"""将 AgentOS 可观测服务 syscall 数据转换为标准 AnalyzeRequest
|
|
13
|
+
|
|
14
|
+
输入格式(来自可观测服务):
|
|
15
|
+
{
|
|
16
|
+
"agent_id": "agent-001",
|
|
17
|
+
"session_id": "session-abc",
|
|
18
|
+
"sandbox_id": "sb-001",
|
|
19
|
+
"os_type": "linux",
|
|
20
|
+
"cwd": "/home/user/project",
|
|
21
|
+
"prompt": "分析日志文件",
|
|
22
|
+
"current_action": {
|
|
23
|
+
"tool": "bash",
|
|
24
|
+
"command": "cat /var/log/app.log",
|
|
25
|
+
"description": "读取应用日志"
|
|
26
|
+
},
|
|
27
|
+
"action_history": [{"tool": "read", "command": "ls -la", "output": "..."}],
|
|
28
|
+
"trace_id": "trace-xyz"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
输出格式:标准 AnalyzeRequest dict
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def adapt(self, raw_data: dict) -> dict:
|
|
35
|
+
"""
|
|
36
|
+
将可观测服务数据转换为标准 AnalyzeRequest dict。
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
raw_data: 可观测服务原始数据
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
dict: 标准 AnalyzeRequest 格式
|
|
43
|
+
"""
|
|
44
|
+
session_id = raw_data.get("session_id") or raw_data.get("agent_id", "unknown")
|
|
45
|
+
|
|
46
|
+
# 可观测服务可能使用 current_action 而非 a_next
|
|
47
|
+
action_raw = raw_data.get("a_next") or raw_data.get("current_action", {})
|
|
48
|
+
if isinstance(action_raw, dict):
|
|
49
|
+
a_next = {
|
|
50
|
+
"action_type": action_raw.get("action_type") or action_raw.get("tool", "unknown"),
|
|
51
|
+
"action_detail": action_raw.get("action_detail") or action_raw.get("command", ""),
|
|
52
|
+
}
|
|
53
|
+
else:
|
|
54
|
+
a_next = {"action_type": "unknown", "action_detail": ""}
|
|
55
|
+
|
|
56
|
+
# 可观测服务可能使用 action_history 中的 tool/command 格式
|
|
57
|
+
history_raw = raw_data.get("action_history", [])
|
|
58
|
+
action_history = []
|
|
59
|
+
for h in history_raw:
|
|
60
|
+
if isinstance(h, dict):
|
|
61
|
+
action_history.append({
|
|
62
|
+
"name": h.get("name") or h.get("action_type") or h.get("tool", ""),
|
|
63
|
+
"action_detail": h.get("action_detail") or h.get("command", ""),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
result = {
|
|
67
|
+
"session_id": session_id,
|
|
68
|
+
"prompt_session": raw_data.get("prompt_session") or raw_data.get("prompt", ""),
|
|
69
|
+
"action_history": action_history,
|
|
70
|
+
"a_next": a_next,
|
|
71
|
+
"reason": raw_data.get("reason") or raw_data.get("current_action", {}).get("description", ""),
|
|
72
|
+
"os_type": raw_data.get("os_type", ""),
|
|
73
|
+
"cwd": raw_data.get("cwd", ""),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# 从 current_action.description 补充 reason
|
|
77
|
+
if not result["reason"] and isinstance(action_raw, dict):
|
|
78
|
+
result["reason"] = action_raw.get("description", "")
|
|
79
|
+
|
|
80
|
+
# 传递 metadata
|
|
81
|
+
metadata = raw_data.get("metadata", {})
|
|
82
|
+
if not metadata:
|
|
83
|
+
metadata = {
|
|
84
|
+
k: raw_data[k] for k in ("agent_id", "sandbox_id", "trace_id", "sandbox_id")
|
|
85
|
+
if k in raw_data and k not in result
|
|
86
|
+
}
|
|
87
|
+
if metadata:
|
|
88
|
+
result["metadata"] = metadata
|
|
89
|
+
|
|
90
|
+
logger.debug("Adapted request for session %s (os_type=%s)", session_id, result.get("os_type", "auto"))
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_raw_format(data: dict) -> bool:
|
|
95
|
+
"""判断数据是否为原始可观测服务格式(需要适配器转换)"""
|
|
96
|
+
# 如果包含 current_action 或缺少 a_next,认为是原始格式
|
|
97
|
+
if "current_action" in data:
|
|
98
|
+
return True
|
|
99
|
+
if "a_next" not in data:
|
|
100
|
+
return True
|
|
101
|
+
# 如果 session_id 缺失但 agent_id 存在,也认为是原始格式
|
|
102
|
+
if "session_id" not in data and "agent_id" in data:
|
|
103
|
+
return True
|
|
104
|
+
return False
|
agent_moss/cli.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Command-line interface for AgentMoss (v2: with --mode socket support)."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def cmd_init(args):
|
|
12
|
+
"""Generate an example input.json template (v2: includes os_type and cwd)."""
|
|
13
|
+
template = {
|
|
14
|
+
"session_id": "my-session-001",
|
|
15
|
+
"prompt_session": "Read /var/log/app.log and analyze errors",
|
|
16
|
+
"action_history": [],
|
|
17
|
+
"a_next": {
|
|
18
|
+
"action_type": "bash",
|
|
19
|
+
"action_detail": "cat /var/log/app.log"
|
|
20
|
+
},
|
|
21
|
+
"reason": "Need to read log file for error analysis",
|
|
22
|
+
"os_type": "",
|
|
23
|
+
"cwd": "/home/user/project",
|
|
24
|
+
}
|
|
25
|
+
output = args.output or "input.json"
|
|
26
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
27
|
+
json.dump(template, f, indent=2, ensure_ascii=False)
|
|
28
|
+
print(f"Template written to {output}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cmd_analyze(args):
|
|
32
|
+
"""Run security analysis on input JSON."""
|
|
33
|
+
from agent_moss.engine.analyzer import analyze
|
|
34
|
+
from agent_moss.profiles import get_profile
|
|
35
|
+
|
|
36
|
+
with open(args.input, "r", encoding="utf-8") as f:
|
|
37
|
+
data = json.load(f)
|
|
38
|
+
|
|
39
|
+
action_history = data.get("action_history", [])
|
|
40
|
+
a_next = data.get("a_next", {})
|
|
41
|
+
|
|
42
|
+
# OS auto-detect
|
|
43
|
+
os_type = data.get("os_type", "")
|
|
44
|
+
profile = get_profile(os_type)
|
|
45
|
+
|
|
46
|
+
result = analyze(
|
|
47
|
+
session_id=data.get("session_id", "unknown"),
|
|
48
|
+
prompt_session=data.get("prompt_session", ""),
|
|
49
|
+
action_history=action_history,
|
|
50
|
+
a_next=a_next,
|
|
51
|
+
reason=data.get("reason", ""),
|
|
52
|
+
profile=profile,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
56
|
+
|
|
57
|
+
if not args.no_file:
|
|
58
|
+
session_id = data.get("session_id", "unknown").replace("/", "_")
|
|
59
|
+
out_dir = Path(args.output_dir) if args.output_dir else Path.cwd()
|
|
60
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
if result["decision"] == "Allow":
|
|
62
|
+
policy_path = out_dir / f"{session_id}_policy.toml"
|
|
63
|
+
policy_path.write_text(result.get("policy", ""), encoding="utf-8")
|
|
64
|
+
print(f"Policy written to {policy_path}")
|
|
65
|
+
else:
|
|
66
|
+
deny_path = out_dir / f"{session_id}_deny_reason.txt"
|
|
67
|
+
reason_text = result.get("violated_policy") or result.get("reason", "")
|
|
68
|
+
deny_path.write_text(reason_text, encoding="utf-8")
|
|
69
|
+
print(f"Deny reason written to {deny_path}")
|
|
70
|
+
|
|
71
|
+
return 0 if result["decision"] == "Allow" else 1
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cmd_server(args):
|
|
75
|
+
"""Start the HTTP API server (supports http and socket modes)."""
|
|
76
|
+
if args.config:
|
|
77
|
+
os.environ["AGENT_MOSS_CONFIG_PATH"] = args.config
|
|
78
|
+
|
|
79
|
+
from agent_moss.server.app import run_server
|
|
80
|
+
|
|
81
|
+
run_server(
|
|
82
|
+
host=args.host,
|
|
83
|
+
port=args.port,
|
|
84
|
+
mode=args.mode,
|
|
85
|
+
socket_path=args.socket,
|
|
86
|
+
)
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main():
|
|
91
|
+
parser = argparse.ArgumentParser(description="AgentMoss security analysis engine")
|
|
92
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
93
|
+
|
|
94
|
+
init_parser = subparsers.add_parser("init", help="Generate input.json template")
|
|
95
|
+
init_parser.add_argument("--output", "-o", help="Output file path", default="input.json")
|
|
96
|
+
|
|
97
|
+
analyze_parser = subparsers.add_parser("analyze", help="Run security analysis on input JSON")
|
|
98
|
+
analyze_parser.add_argument("input", help="Input JSON file path")
|
|
99
|
+
analyze_parser.add_argument("--output-dir", "-o", help="Output directory for policy/deny files")
|
|
100
|
+
analyze_parser.add_argument("--no-file", action="store_true", help="Only print to stdout, don't write files")
|
|
101
|
+
|
|
102
|
+
server_parser = subparsers.add_parser("server", help="Start HTTP API server")
|
|
103
|
+
server_parser.add_argument("--host", default="127.0.0.1", help="Listen host (TCP mode)")
|
|
104
|
+
server_parser.add_argument("--port", type=int, default=9090, help="Listen port (TCP mode)")
|
|
105
|
+
server_parser.add_argument("--config", "-c", help="Path to config YAML file")
|
|
106
|
+
server_parser.add_argument(
|
|
107
|
+
"--mode", choices=["http", "socket"], default="http",
|
|
108
|
+
help="Server mode: http (TCP) or socket (Unix Domain Socket)",
|
|
109
|
+
)
|
|
110
|
+
server_parser.add_argument(
|
|
111
|
+
"--socket", "-s",
|
|
112
|
+
default="/var/run/agent_moss/agent_moss.sock",
|
|
113
|
+
help="Unix socket path (socket mode)",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
args = parser.parse_args()
|
|
117
|
+
|
|
118
|
+
if args.command == "init":
|
|
119
|
+
cmd_init(args)
|
|
120
|
+
elif args.command == "analyze":
|
|
121
|
+
sys.exit(cmd_analyze(args))
|
|
122
|
+
elif args.command == "server":
|
|
123
|
+
sys.exit(cmd_server(args))
|
|
124
|
+
else:
|
|
125
|
+
parser.print_help()
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .coordinator import AgentMossBot, judge_security
|
|
2
|
+
from .heuristic import HeuristicDetector
|
|
3
|
+
from .logic_rules import LogicRulesChecker
|
|
4
|
+
from .llm_analyzer import LLMAnalyzer
|
|
5
|
+
from .skill_engine import SkillEngine
|
|
6
|
+
from .types import HeuristicResult, LogicRuleResult, SecurityJudgment, SkillMatch
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AgentMossBot",
|
|
10
|
+
"HeuristicDetector",
|
|
11
|
+
"LogicRulesChecker",
|
|
12
|
+
"LLMAnalyzer",
|
|
13
|
+
"SkillEngine",
|
|
14
|
+
"SecurityJudgment",
|
|
15
|
+
"HeuristicResult",
|
|
16
|
+
"LogicRuleResult",
|
|
17
|
+
"SkillMatch",
|
|
18
|
+
]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""主入口函数 — 串联 Step 1-2 的完整 AgentMoss 安全分析流程
|
|
2
|
+
|
|
3
|
+
Step 1: AgentMoss Security Agent 安全判断(三层防御)
|
|
4
|
+
├── 1.1 启发式静态检测(关键命令 + 注入检测 + 用户敏感规则)
|
|
5
|
+
├── 1.2 逻辑规则检测(read_before_write + 意图一致性 + 敏感路径 + 危险模式)
|
|
6
|
+
└── 1.3 LLM + Skill 深度分析
|
|
7
|
+
→ 如果 Deny → 直接返回 Deny + 原因(结束)
|
|
8
|
+
|
|
9
|
+
Step 2: Allow → 查缓存 / 生成 policy
|
|
10
|
+
├── 缓存命中 → 使用缓存 policy
|
|
11
|
+
└── 缓存未命中 → LLM 生成 policy + 写入缓存
|
|
12
|
+
→ 返回 Allow + policy
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import tempfile
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from ..profiles.base import OSProfile
|
|
22
|
+
|
|
23
|
+
from ..infra.config import Config, get_default_config, is_policy_gen_enabled
|
|
24
|
+
from ..infra.llm_client import call_llm
|
|
25
|
+
from ..infra.parsers import parse_policy_from_llm
|
|
26
|
+
from ..infra.policy_cache import PolicyCache, get_default_cache
|
|
27
|
+
from ..infra.prompt_templates import get_prompt1
|
|
28
|
+
from ..infra.logging import MossLogger
|
|
29
|
+
from ..profiles import get_profile
|
|
30
|
+
from .coordinator import judge_security
|
|
31
|
+
|
|
32
|
+
# Policy 临时文件输出目录
|
|
33
|
+
POLICY_OUTPUT_DIR = Path(tempfile.gettempdir()) / "agent_moss"
|
|
34
|
+
|
|
35
|
+
# 白名单只读工具的默认最小权限 Policy
|
|
36
|
+
DEFAULT_READONLY_POLICY = """landlock_optional = false
|
|
37
|
+
mount_isolation_fallback = false
|
|
38
|
+
|
|
39
|
+
[path_groups]
|
|
40
|
+
system_binaries = true
|
|
41
|
+
system_libraries = true
|
|
42
|
+
temp_directories = true
|
|
43
|
+
device_files = false
|
|
44
|
+
proc_filesystem = false
|
|
45
|
+
network_config = false
|
|
46
|
+
wsl_paths = false
|
|
47
|
+
|
|
48
|
+
[namespaces]
|
|
49
|
+
mount = true
|
|
50
|
+
pid = false
|
|
51
|
+
network = true
|
|
52
|
+
user = false
|
|
53
|
+
|
|
54
|
+
[resources]
|
|
55
|
+
timeout_secs = 30
|
|
56
|
+
max_memory_bytes = 268435456
|
|
57
|
+
|
|
58
|
+
[environment]
|
|
59
|
+
whitelist = ["PATH", "LANG", "HOME", "USER", "HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy"]
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def analyze(
|
|
64
|
+
session_id: str,
|
|
65
|
+
prompt_session: str,
|
|
66
|
+
action_history: list[dict[str, object]],
|
|
67
|
+
a_next: dict[str, str],
|
|
68
|
+
reason: str,
|
|
69
|
+
config: Config | None = None,
|
|
70
|
+
cache: PolicyCache | None = None,
|
|
71
|
+
profile: "OSProfile | None" = None,
|
|
72
|
+
) -> dict[str, str]:
|
|
73
|
+
"""
|
|
74
|
+
分析 agent 的下一步动作是否在安全范围内,并在允许时生成最小 policy。
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
session_id: 会话唯一标识
|
|
78
|
+
prompt_session: 用户输入的原始 prompt
|
|
79
|
+
action_history: 历史动作序列
|
|
80
|
+
a_next: 下一步动作,含 action_type 和 action_detail
|
|
81
|
+
reason: 执行该动作的理由
|
|
82
|
+
config: 配置项,为 None 时使用默认配置
|
|
83
|
+
cache: Policy 缓存实例,为 None 时使用默认缓存
|
|
84
|
+
profile: OS Profile(可选,默认自动检测)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
dict: {"decision": "Allow"|"Deny", "policy": "...", "reason": "...", ...}
|
|
88
|
+
"""
|
|
89
|
+
cfg = config or get_default_config()
|
|
90
|
+
os_profile = profile or get_profile()
|
|
91
|
+
moss_log = MossLogger(session_id)
|
|
92
|
+
max_retries = cfg.retry.max_retries
|
|
93
|
+
retry_interval = cfg.retry.retry_interval
|
|
94
|
+
|
|
95
|
+
moss_log.log_input(prompt_session, action_history, a_next, reason)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# ==================== Step 1: AgentMoss Security Agent 安全判断 ====================
|
|
99
|
+
moss_log.start_step("step1_security_judge")
|
|
100
|
+
judgment = judge_security(
|
|
101
|
+
prompt_session=prompt_session,
|
|
102
|
+
action_history=action_history,
|
|
103
|
+
a_next=a_next,
|
|
104
|
+
reason=reason,
|
|
105
|
+
config=cfg,
|
|
106
|
+
profile=os_profile,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if not judgment.allowed:
|
|
110
|
+
layers_str = ", ".join(judgment.violated_layers) if judgment.violated_layers else judgment.source
|
|
111
|
+
moss_log.end_step(
|
|
112
|
+
"step1_security_judge",
|
|
113
|
+
detail=f"allowed=False, risk_level={judgment.risk_level}, violated_layers=[{layers_str}]",
|
|
114
|
+
)
|
|
115
|
+
result = {
|
|
116
|
+
"decision": "Deny",
|
|
117
|
+
"policy": "",
|
|
118
|
+
"reason": judgment.reason,
|
|
119
|
+
"violated_policy": f"[{judgment.risk_type}] {judgment.reason}",
|
|
120
|
+
"violated_layers": judgment.violated_layers,
|
|
121
|
+
"risk_level": judgment.risk_level,
|
|
122
|
+
"risk_type": judgment.risk_type,
|
|
123
|
+
"confidence": judgment.confidence,
|
|
124
|
+
}
|
|
125
|
+
moss_log.log_result("Deny", result["reason"])
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
moss_log.end_step(
|
|
129
|
+
"step1_security_judge",
|
|
130
|
+
detail=f"allowed=True, risk_level={judgment.risk_level}, source={judgment.source}",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# ==================== Step 2: Allow → 查缓存 / 生成 policy ====================
|
|
134
|
+
policy_cache = cache or get_default_cache()
|
|
135
|
+
enable_policy_gen = is_policy_gen_enabled()
|
|
136
|
+
|
|
137
|
+
moss_log.start_step("step2_cache_lookup")
|
|
138
|
+
cached_policy = policy_cache.get(session_id, prompt_session)
|
|
139
|
+
|
|
140
|
+
if cached_policy is not None:
|
|
141
|
+
moss_log.end_step("step2_cache_lookup", detail="缓存命中")
|
|
142
|
+
policy_a_next = cached_policy
|
|
143
|
+
_ = _save_policy_to_file(session_id, policy_a_next)
|
|
144
|
+
elif not enable_policy_gen:
|
|
145
|
+
moss_log.end_step("step2_cache_lookup", detail="缓存未命中,Policy 生成已禁用")
|
|
146
|
+
moss_log.start_step("step2_default_policy")
|
|
147
|
+
policy_a_next = DEFAULT_READONLY_POLICY
|
|
148
|
+
policy_cache.put(session_id, prompt_session, policy_a_next)
|
|
149
|
+
_ = _save_policy_to_file(session_id, policy_a_next)
|
|
150
|
+
moss_log.end_step("step2_default_policy", detail="使用默认最小权限 Policy")
|
|
151
|
+
result = {
|
|
152
|
+
"decision": "Allow",
|
|
153
|
+
"policy": policy_a_next,
|
|
154
|
+
"reason": judgment.reason,
|
|
155
|
+
"violated_policy": "",
|
|
156
|
+
"risk_level": judgment.risk_level,
|
|
157
|
+
"risk_type": judgment.risk_type,
|
|
158
|
+
"confidence": judgment.confidence,
|
|
159
|
+
}
|
|
160
|
+
moss_log.log_result("Allow", result["reason"], policy_a_next)
|
|
161
|
+
return result
|
|
162
|
+
else:
|
|
163
|
+
moss_log.end_step("step2_cache_lookup", detail="缓存未命中,启用 LLM 生成")
|
|
164
|
+
|
|
165
|
+
if judgment.source == "whitelist_bypass":
|
|
166
|
+
moss_log.start_step("step2_whitelist_policy")
|
|
167
|
+
policy_a_next = DEFAULT_READONLY_POLICY
|
|
168
|
+
policy_cache.put(session_id, prompt_session, policy_a_next)
|
|
169
|
+
_ = _save_policy_to_file(session_id, policy_a_next)
|
|
170
|
+
moss_log.end_step("step2_whitelist_policy", detail="白名单工具使用预定义最小权限 Policy")
|
|
171
|
+
result = {
|
|
172
|
+
"decision": "Allow",
|
|
173
|
+
"policy": policy_a_next,
|
|
174
|
+
"reason": judgment.reason,
|
|
175
|
+
"violated_policy": "",
|
|
176
|
+
"risk_level": judgment.risk_level,
|
|
177
|
+
"risk_type": judgment.risk_type,
|
|
178
|
+
"confidence": judgment.confidence,
|
|
179
|
+
}
|
|
180
|
+
moss_log.log_result("Allow", result["reason"], policy_a_next)
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
if cfg.timeout.step_interval > 0:
|
|
184
|
+
time.sleep(cfg.timeout.step_interval)
|
|
185
|
+
|
|
186
|
+
moss_log.start_step("step2_generate_policy")
|
|
187
|
+
prompt1_text = get_prompt1(prompt_session)
|
|
188
|
+
step2_error: Exception | None = None
|
|
189
|
+
policy_a_next = None
|
|
190
|
+
|
|
191
|
+
for attempt in range(1, max_retries + 1):
|
|
192
|
+
try:
|
|
193
|
+
llm_response = call_llm(
|
|
194
|
+
prompt=prompt1_text,
|
|
195
|
+
timeout=cfg.timeout.prompt1_timeout,
|
|
196
|
+
config=cfg.llm,
|
|
197
|
+
)
|
|
198
|
+
policy_a_next = parse_policy_from_llm(llm_response)
|
|
199
|
+
moss_log.log_llm_interaction(
|
|
200
|
+
"step2_generate_policy",
|
|
201
|
+
prompt1_text[:200],
|
|
202
|
+
llm_response[:200],
|
|
203
|
+
)
|
|
204
|
+
policy_cache.put(session_id, prompt_session, policy_a_next)
|
|
205
|
+
_ = _save_policy_to_file(session_id, policy_a_next)
|
|
206
|
+
moss_log.end_step("step2_generate_policy", detail="Policy 生成并缓存成功")
|
|
207
|
+
step2_error = None
|
|
208
|
+
break
|
|
209
|
+
except (TimeoutError, ValueError, Exception) as e:
|
|
210
|
+
step2_error = e
|
|
211
|
+
if attempt < max_retries:
|
|
212
|
+
moss_log.log_error("step2_generate_policy", e)
|
|
213
|
+
moss_log.start_step(f"step2_retry_{attempt}")
|
|
214
|
+
time.sleep(retry_interval)
|
|
215
|
+
moss_log.end_step(
|
|
216
|
+
f"step2_retry_{attempt}",
|
|
217
|
+
detail=f"第 {attempt} 次重试,等待 {retry_interval}s",
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
moss_log.log_error("step2_generate_policy", e)
|
|
221
|
+
|
|
222
|
+
if step2_error is not None or policy_a_next is None:
|
|
223
|
+
if isinstance(step2_error, TimeoutError):
|
|
224
|
+
moss_log.end_step(
|
|
225
|
+
"step2_generate_policy",
|
|
226
|
+
detail=f"超时(已重试 {max_retries} 次)",
|
|
227
|
+
)
|
|
228
|
+
result = {
|
|
229
|
+
"decision": "Deny",
|
|
230
|
+
"policy": "",
|
|
231
|
+
"reason": f"安全检测通过,但策略生成超时(已重试 {max_retries} 次),出于安全考虑拒绝执行",
|
|
232
|
+
"violated_policy": "无法生成策略:LLM 调用超时",
|
|
233
|
+
}
|
|
234
|
+
elif isinstance(step2_error, ValueError):
|
|
235
|
+
moss_log.end_step(
|
|
236
|
+
"step2_generate_policy",
|
|
237
|
+
detail=f"解析失败(已重试 {max_retries} 次): {step2_error}",
|
|
238
|
+
)
|
|
239
|
+
result = {
|
|
240
|
+
"decision": "Deny",
|
|
241
|
+
"policy": "",
|
|
242
|
+
"reason": f"安全检测通过,但策略生成失败(已重试 {max_retries} 次):{step2_error}",
|
|
243
|
+
"violated_policy": "生成的策略格式无效",
|
|
244
|
+
}
|
|
245
|
+
else:
|
|
246
|
+
moss_log.end_step(
|
|
247
|
+
"step2_generate_policy",
|
|
248
|
+
detail=f"异常(已重试 {max_retries} 次): {step2_error}",
|
|
249
|
+
)
|
|
250
|
+
result = {
|
|
251
|
+
"decision": "Deny",
|
|
252
|
+
"policy": "",
|
|
253
|
+
"reason": f"安全检测通过,但策略生成服务异常(已重试 {max_retries} 次):{step2_error}",
|
|
254
|
+
"violated_policy": "无法生成策略:LLM 服务异常",
|
|
255
|
+
}
|
|
256
|
+
moss_log.log_result("Deny", result["reason"])
|
|
257
|
+
return result
|
|
258
|
+
|
|
259
|
+
result = {
|
|
260
|
+
"decision": "Allow",
|
|
261
|
+
"policy": policy_a_next,
|
|
262
|
+
"reason": judgment.reason,
|
|
263
|
+
"violated_policy": "",
|
|
264
|
+
"risk_level": judgment.risk_level,
|
|
265
|
+
"risk_type": judgment.risk_type,
|
|
266
|
+
"confidence": judgment.confidence,
|
|
267
|
+
}
|
|
268
|
+
moss_log.log_result("Allow", result["reason"], policy_a_next)
|
|
269
|
+
return result
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
moss_log.log_error("unknown", e)
|
|
273
|
+
result = {
|
|
274
|
+
"decision": "Deny",
|
|
275
|
+
"policy": "",
|
|
276
|
+
"reason": f"分析过程发生未预期异常:{e}",
|
|
277
|
+
"violated_policy": "未知异常",
|
|
278
|
+
}
|
|
279
|
+
moss_log.log_result("Deny", result["reason"])
|
|
280
|
+
return result
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _save_policy_to_file(session_id: str, policy_toml: str) -> Path:
|
|
284
|
+
POLICY_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
285
|
+
safe_name = session_id.replace("/", "_").replace("\\", "_")
|
|
286
|
+
filepath = POLICY_OUTPUT_DIR / f"{safe_name}.toml"
|
|
287
|
+
_ = filepath.write_text(policy_toml, encoding="utf-8")
|
|
288
|
+
return filepath
|