mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 04:40:45 +08:00
将高风险 public 工具开关改为按用户生效。
在 Admin 工作区路径页新增并保存用户级 public_tools_allow_high,扩展用户路径策略存储字段并让 catalog 风险门控优先读取该用户策略(环境变量仍可覆盖)。 Made-with: Cursor
This commit is contained in:
parent
e06184cc2f
commit
5ef9abb069
4 changed files with 81 additions and 11 deletions
|
|
@ -1401,8 +1401,18 @@ def build_admin_router() -> APIRouter:
|
|||
raise HTTPException(status_code=403, detail="workspace_paths_self_only")
|
||||
row = store.get_user_workspace_path_allowlist(tenant_id=tid, user_id=uid)
|
||||
if not row:
|
||||
return {"ok": True, "from_db": False, "policy": {"extra_roots": "", "allow_any_path": False}}
|
||||
return {"ok": True, "from_db": True, "policy": row}
|
||||
return {
|
||||
"ok": True,
|
||||
"from_db": False,
|
||||
"policy": {"extra_roots": "", "allow_any_path": False},
|
||||
"public_tools_allow_high": False,
|
||||
}
|
||||
return {
|
||||
"ok": True,
|
||||
"from_db": True,
|
||||
"policy": row,
|
||||
"public_tools_allow_high": bool(row.get("allow_high_risk_public_tools")),
|
||||
}
|
||||
|
||||
@router.post("/admin/api/users/workspace-path-policy")
|
||||
def api_users_workspace_path_policy_save(
|
||||
|
|
@ -1429,11 +1439,13 @@ def build_admin_router() -> APIRouter:
|
|||
if err:
|
||||
return {"ok": False, "error": err}
|
||||
allow_any = bool(payload.get("allow_any_path", False))
|
||||
allow_high = bool(payload.get("public_tools_allow_high", False))
|
||||
store.upsert_user_workspace_path_allowlist(
|
||||
tenant_id=tid,
|
||||
user_id=uid,
|
||||
extra_roots=norm,
|
||||
allow_any_path=allow_any,
|
||||
allow_high_risk_public_tools=allow_high,
|
||||
)
|
||||
store.add_admin_audit_log(
|
||||
actor_tenant_id=ctx["tenant_id"],
|
||||
|
|
@ -1442,10 +1454,15 @@ def build_admin_router() -> APIRouter:
|
|||
target_type="user",
|
||||
target_id=uid,
|
||||
status="ok",
|
||||
detail={"tenant_id": tid, "allow_any_path": allow_any, "extra_roots_preview": norm[:500]},
|
||||
detail={
|
||||
"tenant_id": tid,
|
||||
"allow_any_path": allow_any,
|
||||
"public_tools_allow_high": bool(allow_high),
|
||||
"extra_roots_preview": norm[:500],
|
||||
},
|
||||
)
|
||||
row = store.get_user_workspace_path_allowlist(tenant_id=tid, user_id=uid)
|
||||
return {"ok": True, "policy": row or {}}
|
||||
return {"ok": True, "policy": row or {}, "public_tools_allow_high": bool((row or {}).get("allow_high_risk_public_tools"))}
|
||||
|
||||
@router.post("/admin/api/users/delete-unbound")
|
||||
def api_users_delete_unbound(
|
||||
|
|
|
|||
|
|
@ -208,6 +208,9 @@ const I18N = {
|
|||
"workspacePaths.allowAny": "允许任意路径(高风险)",
|
||||
"workspacePaths.allowAnyHint":
|
||||
"仅作用于内置工作区工具(read_file / glob 等)的路径校验;不会放开 MCP filesystem,也不会自动把整盘写进 MCP。需要 MCP 列目录的盘符/目录请填在「额外根路径」或环境变量 AIA_WORKSPACE_EXTRA_ROOTS。",
|
||||
"workspacePaths.allowHighTools": "允许高风险 Public 工具(全局)",
|
||||
"workspacePaths.allowHighToolsHint":
|
||||
"开启后将放开 run_command / write_file / edit_file 等高风险 public 工具的模型可见性(等效 AIA_PUBLIC_TOOLS_ALLOW_HIGH=1)。",
|
||||
"workspacePaths.load": "加载",
|
||||
"workspacePaths.save": "保存",
|
||||
"workspacePaths.status": "状态",
|
||||
|
|
@ -624,6 +627,9 @@ const I18N = {
|
|||
"workspacePaths.allowAny": "Allow any path (high risk)",
|
||||
"workspacePaths.allowAnyHint":
|
||||
"Applies only to built-in workspace tools (read_file / glob, etc.); it does not unlock MCP filesystem or auto-mount whole disks for MCP. List directories you need in “Extra roots” or AIA_WORKSPACE_EXTRA_ROOTS.",
|
||||
"workspacePaths.allowHighTools": "Allow high-risk public tools (global)",
|
||||
"workspacePaths.allowHighToolsHint":
|
||||
"When enabled, high-risk public tools (run_command / write_file / edit_file, etc.) become model-visible (equivalent to AIA_PUBLIC_TOOLS_ALLOW_HIGH=1).",
|
||||
"workspacePaths.load": "Load",
|
||||
"workspacePaths.save": "Save",
|
||||
"workspacePaths.status": "Status",
|
||||
|
|
@ -6856,8 +6862,13 @@ async function renderWorkspacePaths() {
|
|||
style: "min-height:88px;font-family:monospace;",
|
||||
});
|
||||
const allowAnyCb = el("input", { type: "checkbox" });
|
||||
const allowHighToolsCb = el("input", { type: "checkbox" });
|
||||
const status = el("div", { class: "muted", text: "" });
|
||||
const canWrite = hasPermission("admin:user:write") || canWsWrite;
|
||||
const canTogglePublicHigh = hasPermission("admin:user:write");
|
||||
if (!canTogglePublicHigh) {
|
||||
allowHighToolsCb.disabled = true;
|
||||
}
|
||||
|
||||
const getEffectiveTid = () => (selfService ? sessionTid : String(tenantSel.value || ""));
|
||||
const getEffectiveUid = () => (selfService ? sessionUid : String(userSel.value || ""));
|
||||
|
|
@ -6904,6 +6915,7 @@ async function renderWorkspacePaths() {
|
|||
const pol = r.policy || {};
|
||||
extraInput.value = String(pol.extra_roots || "");
|
||||
allowAnyCb.checked = !!pol.allow_any_path;
|
||||
allowHighToolsCb.checked = !!r.public_tools_allow_high;
|
||||
status.textContent = (r.from_db ? t("workspacePaths.fromDb") + " · " : "") + JSON.stringify(pol);
|
||||
} catch (e) {
|
||||
status.textContent = String(e && e.message ? e.message : e);
|
||||
|
|
@ -6937,12 +6949,16 @@ async function renderWorkspacePaths() {
|
|||
if (!tid || !uid) return;
|
||||
status.textContent = "…";
|
||||
try {
|
||||
const r = await apiPost("/admin/api/users/workspace-path-policy", {
|
||||
const payload = {
|
||||
tenant_id: tid,
|
||||
user_id: uid,
|
||||
extra_roots: extraInput.value,
|
||||
allow_any_path: !!allowAnyCb.checked,
|
||||
});
|
||||
};
|
||||
if (canTogglePublicHigh) {
|
||||
payload.public_tools_allow_high = !!allowHighToolsCb.checked;
|
||||
}
|
||||
const r = await apiPost("/admin/api/users/workspace-path-policy", payload);
|
||||
if (!r.ok) {
|
||||
status.textContent = String(r.error || "error");
|
||||
return;
|
||||
|
|
@ -6979,6 +6995,11 @@ async function renderWorkspacePaths() {
|
|||
el("span", { text: t("workspacePaths.allowAny") }),
|
||||
]),
|
||||
el("div", { class: "muted", style: "margin-top:6px;line-height:1.45;", text: t("workspacePaths.allowAnyHint") }),
|
||||
el("label", { class: "row", style: "align-items:center;gap:8px;margin-top:10px;" }, [
|
||||
allowHighToolsCb,
|
||||
el("span", { text: t("workspacePaths.allowHighTools") }),
|
||||
]),
|
||||
el("div", { class: "muted", style: "margin-top:6px;line-height:1.45;", text: t("workspacePaths.allowHighToolsHint") }),
|
||||
el("div", { class: "row", style: "margin-top:10px;gap:8px;" }, [loadBtn, saveBtn]),
|
||||
el("div", { class: "muted", style: "margin-top:8px;" }, [el("span", { text: t("workspacePaths.status") + ": " }), status]),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -544,6 +544,7 @@ class SqliteStore:
|
|||
user_id TEXT NOT NULL,
|
||||
extra_roots TEXT NOT NULL DEFAULT '',
|
||||
allow_any_path INTEGER NOT NULL DEFAULT 0,
|
||||
allow_high_risk_public_tools INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (tenant_id, user_id),
|
||||
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
|
||||
|
|
@ -551,6 +552,11 @@ class SqliteStore:
|
|||
);
|
||||
"""
|
||||
)
|
||||
uw_cols = {row[1] for row in conn.execute("PRAGMA table_info(user_workspace_path_allowlist)").fetchall()}
|
||||
if "allow_high_risk_public_tools" not in uw_cols:
|
||||
conn.execute(
|
||||
"ALTER TABLE user_workspace_path_allowlist ADD COLUMN allow_high_risk_public_tools INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS auth_session (
|
||||
|
|
@ -4553,7 +4559,7 @@ class SqliteStore:
|
|||
with self._connect() as conn:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT tenant_id, user_id, extra_roots, allow_any_path, updated_at
|
||||
SELECT tenant_id, user_id, extra_roots, allow_any_path, allow_high_risk_public_tools, updated_at
|
||||
FROM user_workspace_path_allowlist
|
||||
WHERE tenant_id = ? AND user_id = ?
|
||||
LIMIT 1
|
||||
|
|
@ -4567,6 +4573,7 @@ class SqliteStore:
|
|||
"user_id": str(row["user_id"] or ""),
|
||||
"extra_roots": str(row["extra_roots"] or ""),
|
||||
"allow_any_path": bool(int(row["allow_any_path"] or 0)),
|
||||
"allow_high_risk_public_tools": bool(int(row["allow_high_risk_public_tools"] or 0)),
|
||||
"updated_at": str(row["updated_at"] or ""),
|
||||
}
|
||||
|
||||
|
|
@ -4597,6 +4604,7 @@ class SqliteStore:
|
|||
user_id: str,
|
||||
extra_roots: str,
|
||||
allow_any_path: bool,
|
||||
allow_high_risk_public_tools: bool = False,
|
||||
) -> None:
|
||||
tid = str(tenant_id or "").strip()
|
||||
uid = str(user_id or "").strip()
|
||||
|
|
@ -4609,14 +4617,24 @@ class SqliteStore:
|
|||
with self._connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO user_workspace_path_allowlist (tenant_id, user_id, extra_roots, allow_any_path, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT INTO user_workspace_path_allowlist (
|
||||
tenant_id, user_id, extra_roots, allow_any_path, allow_high_risk_public_tools, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(tenant_id, user_id) DO UPDATE SET
|
||||
extra_roots = excluded.extra_roots,
|
||||
allow_any_path = excluded.allow_any_path,
|
||||
allow_high_risk_public_tools = excluded.allow_high_risk_public_tools,
|
||||
updated_at = excluded.updated_at
|
||||
""",
|
||||
(tid, uid, roots, 1 if allow_any_path else 0, ts),
|
||||
(
|
||||
tid,
|
||||
uid,
|
||||
roots,
|
||||
1 if allow_any_path else 0,
|
||||
1 if allow_high_risk_public_tools else 0,
|
||||
ts,
|
||||
),
|
||||
)
|
||||
|
||||
def create_auth_session(
|
||||
|
|
|
|||
|
|
@ -273,7 +273,21 @@ def materialize_tool_specs(
|
|||
# Optional safety gate for public tools.
|
||||
# Default: only allow low risk public tools to be visible to all roles.
|
||||
# Override via env: AIA_PUBLIC_TOOLS_ALLOW_HIGH=1 to allow high risk public tools.
|
||||
allow_high = _is_truthy(os.getenv("AIA_PUBLIC_TOOLS_ALLOW_HIGH", "0"))
|
||||
# If env is unset, fallback to per-user workspace path policy switch.
|
||||
raw_env = str(os.getenv("AIA_PUBLIC_TOOLS_ALLOW_HIGH") or "").strip()
|
||||
if raw_env:
|
||||
allow_high = _is_truthy(raw_env)
|
||||
else:
|
||||
allow_high = False
|
||||
try:
|
||||
if store is not None and path_policy_tenant_id and path_policy_user_id:
|
||||
row = store.get_user_workspace_path_allowlist(
|
||||
tenant_id=str(path_policy_tenant_id),
|
||||
user_id=str(path_policy_user_id),
|
||||
)
|
||||
allow_high = bool((row or {}).get("allow_high_risk_public_tools"))
|
||||
except Exception:
|
||||
allow_high = False
|
||||
if allow_high:
|
||||
return True
|
||||
return str(getattr(spec, "risk_level", "") or "low").strip().lower() != "high"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue