agentscope-ai / agentscope-ai/AgentTeams

Design: L2 permission & capability model (worker config, channels, approval, skill catalog)

Offen
#1,220 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
Vorherrschende Sprache
Go
Sterne
5.6k
Forks
692
Ø Merge
5 T. 4 Std.
Gemergte PRs (30 T.)
23

Beschreibung

# Design: L2 permission & capability model (worker config, channels, approval, skill catalog)

Related: #1217 (L2 team-scoped workspace access) · #1212 (L2 scoped worker update) · #1216 (L2 approval level) · #1219 (worker channels API) · #1211 (skill catalog API)

Per the review feedback on those PRs, this issue consolidates the permission model they all depend on, for community discussion before implementation continues. **Status of this draft: the maintainer review points are fully incorporated; the capability model, secret contract, audit design, and leader model below are the proposed design and are open for discussion.**

---

## 1. Context

L2 users (Human CRs with `accessibleTeams`) operate their own teams. The current implementation lets every L2 user touch the same fields of every in-team worker through a generic update boundary. The direction (L2 self-service within own teams) is kept; the boundary model changes:

- **Roles × capabilities × fine-grained actions** instead of one generic `ActionUpdate` for all L2 writes.
- **A uniform contract for sensitive data** (masked reads, write-only, preserve-on-omit, audited reveal-if-ever).
- **A defined source of truth for the skill catalog** (not reconstructed from Worker assignments).
- **An explicit leader (agent) model** and **a durable audit trail**.

Two permission planes exist and are deliberately separate:

| Plane | Scope | Status |
|---|---|---|
| **API actions** (this design) | who may do what to which resource via the controller API | defined below |
| **Room management** (Matrix power levels, #1210) | who may rename/invite/kick in which room | already settled: L1=100, L2/L3=50 |

Having room-level 50 does not imply any API capability, and vice versa.

## 2. Roles (unchanged)

| Role | Source | Team scope |
|---|---|---|
| admin / manager | `permissionLevel: 1` / built-in | all (implicitly holds every capability) |
| L2 human | Human CR, `permissionLevel: 2` + `accessibleTeams` | own teams |
| L3 human | Human CR, `permissionLevel: 3` + `accessibleWorkers` | scoped workers — **read-only** (channel/approval reads for the scoped worker; no writes) |
| team leader (agent) | Worker CR listed as `team_leader` member in the Team CR | own team (see §5) |
| worker | Worker CR | self (unchanged) |

## 3. Capability model

**Field.** New optional `capabilities` list on the Human CR (e.g. `["channel_secrets"]`). List-shaped rather than a boolean so the value set can grow without a schema change; each value is a named grant.

**Value set (proposed, complete for v1):**

| Value | Grants |
|---|---|
| `full_access` | meta-value: implies all others. `permissionLevel: 1` implies all capabilities. |
| `channel_secrets` | writing channel credentials (§6.1) |
| `external_sources` | adding/managing arbitrary external skill registries and their credentials (`remoteSkills`) |
| `approval_policy` | setting `approval_level=OFF` (disables Tool Guard) — the guarded modes remain default L2 |
| `secret_reveal` | reserved for a plaintext-reveal operation (§6.4). **Not implemented in v1**; the value is documented so the grant model is stable when it ships. |

Unknown values are rejected at admission. The value set is defined in code and documented here; adding a value is an additive change (code + docs), not a schema change.

**Grant / revoke.** Via the existing human-update path (admin/manager only): a `capabilities` field on the update body with merge-patch semantics — omitted = unchanged, explicit list = replaces, explicit empty = clears. Humans cannot grant themselves (self-update stays admin-only). Every grant/revoke is audited (§8).

**Check order.** An action is allowed only if: role baseline (admin/manager always) **AND** team scope (the target is in `accessibleTeams`) **AND** the required capability (default L2 for non-sensitive, the named capability for sensitive). A capability alone never implies team scope.

**Why a list, why this granularity.** A single `fullAccess` boolean is functionally identical today; the list keeps future splits (e.g. separating channel credentials from approval policy) to additive value additions, and the audit trail can record exactly which capability authorized an operation. The v1 value set covers every sensitive surface identified in the reviews — no placeholder values, no pre-emptive granularity.

## 4. Role/capability matrix (L2 view; admin/manager may do everything and are not listed)

| Operation | Default L2 (own teams) | Capability required |
|---|:--:|---|
| Read worker config | ✅ | — |
| Update non-sensitive worker fields: `skills` (catalog assignment: builtin + team layer), `mcpServers` addresses (bearer attached only to trusted gateway hosts, §7), display fields | ✅ | — |
| Add/manage arbitrary external skill registries (credential-bearing source URIs) | — | `external_sources` |
| Read channel metadata (type, endpoint, enabled, display, health) with secrets masked | ✅ | — |
| Update non-sensitive channel fields; restart channel | ✅ | — |
| Write channel credentials | — | `channel_secrets` (write-only) |
| Reveal credential plaintext | — | `secret_reveal` + audited (not in v1, §6.4) |
| Read approval level; switch among guarded modes (`STRICT`/`SMART`/`AUTO`) | ✅ | — |
| Set `approval_level=OFF` | — | `approval_policy` + audited |
| List the team skill catalog (`GET /skills?team=`, own teams) | ✅ | — |
| Upload a skill to the team layer (`POST /skills` `scope=team`) | ✅ | — (role AND scope; compensation package: scan gate, audit, source/uploader marking, warn-to-admin — §10) |
| Deployment-level (individual) catalog (`GET /skills` no param) and `scope=deployment` upload | — | L1 (admin) only (§10) |
| Worker `env`, image, other privileged spec fields | — | admin-only (not even `full_access`) |

Any field of the update request type not explicitly decided is denied (deny-by-default, §9).

## 5. Leader (agent) model

Two subjects, two lines:

**The human who owns a leader.** There is no per-leader "owner" field in the CRD; ownership is team scope — a human with the team in `accessibleTeams` may read and (per the default L2 row) update every worker in the team, **including the leader itself** (the leader is a Worker CR). No extra mechanism. (If exclusive ownership is ever wanted, that is a new CRD field, out of scope here.)

**The leader agent over its workers.** "Instructing" workers is the Matrix plane (@mention / TeamHarness dispatch) and is unchanged by this design. "Reading and writing" is the API plane, where the design pins down what is today an implementation accident (the L2 field whitelist currently applies to humans only):

- The leader's worker updates are **restricted to the same non-sensitive allowlist as default L2** (skills / MCP addresses) plus its existing lifecycle actions (wake/sleep/ensure-ready). Full-spec writes (image, env, persona) stay owner-level.
- The leader never holds capabilities: capabilities are granted to human accounts only.

## 6. Sensitive data

### 6.1 Classification

- **Channel credentials**: `client_secret`, `app_secret`, `access_token`, `bot_token`, `password`, WeChat QR session state.
- **External source credentials**: registry tokens embedded in `remoteSkills` source URIs, registry auth blocks.
- **Gateway keys**: the Worker Gateway consumer key currently injected into every MCP entry (§7).
- **Worker `env`**: may carry arbitrary keys — stays admin-only.

### 6.2 Uniform contract

1. **Reads return masked values or `configured: true`** — never plaintext, on the normal read path for all roles.
2. **Secret fields are write-only.**
3. **Omitting a secret in an update preserves the existing value** (nil-pointer merge-patch); an explicit empty value clears it.
4. **Plaintext reveal is not in v1.** It is the system's only plaintext egress — once read, the value lives in screens/logs/history and cannot be un-read. The real workflows are covered without it: setting = write-only, validity check = channel health, rotation = write the new value. **The door is kept:** if a real workflow emerges, reveal ships as a separately authorized and audited operation (dedicated action, `secret_reveal` capability, one-time token in the response, per-call audit). No CRD change is needed to add it later.

### 6.3 QR credentials

Ephemeral by design: they live only in the worker's qwenpaw instance. The controller passes the QR and its status poll through without persisting to MinIO, CR status, or logs. Pinned as contract.

### 6.4 Reveal decision (recorded)

v1: no reveal endpoint. Rationale and the future shape are recorded in §6.2(4) so the decision is revisitable without redesign.

## 7. MCP bearer attachment

`GenerateMcporterConfig` currently injects `Authorization: Bearer ` into every MCP entry using the URL verbatim — with L2-writable `mcpServers`, an entry pointing at an attacker-controlled URL exfiltrates the gateway consumer key. Design:

- **Trusted-gateway gate (v1):** the bearer is attached only when the entry's host matches the controller-configured trusted gateway host(s); other entries are written without credentials and the API response carries a warning.
- **credentialRef (longer term):** MCP entries reference credentials instead of embedding them.
- **Interim state of the pending #1212 branch:** `mcpServers` is temporarily removed from the L2-writable set until the gate lands; the target state above (MCP address editing remains a default-L2 capability) is the intended final model.

**MCP follow-up items (2026-09-11, proposed):** ① a deployment-level MCP catalog (`GET /api/v1/mcp-servers` — trusted gateways plus the shared MCP registry, each entry carrying `timeout` / `headers` / `trusted`), mirroring the skill-catalog pattern, so workbenches show a deployment-MCP × Worker matrix instead of browsing per-worker entries; proposed for after PR B, since the `trusted` flag is a product of the bearer gate. ② CRD: `spec.mcpServers` entries should carry a per-server timeout — the qwenpaw 2.2.x worker already honours a configurable `http_timeout` (default 30s) with no controller-side counterpart, and the existing dashboard-side private MCP registry model already fields `timeout` / `headers` that the controller API lacks; moving that surface to the controller API is part of item ①.

## 8. Audit

Two layers, no new services:

1. **Structured logs** (every security-relevant event, immediately).
2. **Durable append-only store**: one JSON line per event to a daily MinIO object (e.g. `audit/2026-09-03.jsonl`); object versioning optional; read-only from the workbench later. MinIO is an existing dependency.

Events requiring an audit record (who / when / target / action / authorizing capability / before-after values — **never secret values**):

- capability grant/revoke
- `approval_level` change (`OFF` additionally records the authorizing capability)
- channel credential write (field names only)
- external source add/modify (source URI redacted)
- (reserved) secret reveal, if ever shipped

## 9. Dedicated actions & deny-by-default

Scoped actions replace the generic `ActionUpdate` on L2/leader write paths:

`ActionWorkerConfigUpdate` (non-sensitive) · `ActionExternalSourceManage` (`external_sources`) · `ActionChannelRead` · `ActionChannelUpdate` · `ActionChannelSecrets` (`channel_secrets`) · `ActionApprovalRead` · `ActionWorkerApproval` (already introduced in #1216; `OFF` additionally requires `approval_policy`) · `ActionSkillCatalogList` (explicit; already landed in #1211) · `ActionSkillAssign`.

Field policies are allowlists (deny-by-default when the request type grows), with a reflection-based full-field probe test pinning coverage — already landed on the #1212 branch.

## 10. Skill catalog source of truth (#1211)

The catalog is read directly by the controller from MinIO + the agent-template directories — **not** reconstructed from `Worker.spec.skills` / `spec.remoteSkills` (which couples catalog contents to current assignments and can expose cross-team registry details and credential-bearing source URIs).

**Skill layering (final, 2026-09-11).** Skills live in four layers:

1. **builtin** — code-bundled template skills, scanned from the deployer's agent-template directories (per-runtime availability derived from the deployer's own template-selection function, so the catalog never drifts from what workers actually receive).
2. **Individual (deployment-level)** — `agents/global/skills/` (catalog `source="shared"`, value retained) plus the Manager's seed skills (root-level `skills/`, `skills-alpha/`). These are the Manager/admin's **individual** assets — managed by **L1 (admin) only** (skills + MCP; the MCP half stays on the existing manager config surface). The Manager agent is the *managed object* of this layer, not a manager of it.
3. **Team** — `teams//skills/` (team-space root, symmetric with `agents//skills/`); readable/writable by L1 (all teams) and L2 (own team); **the Manager agent does not participate in any team-skill path** (explicit declaration, to prevent future designs treating the Manager as a team-skill manager).
4. **Worker** — `agents//skills/`, the materialized copy layer; the only layer that becomes prompt content.

**Read contract (scope decided by authz, not by request shape — zero deployment-mode detection):**

| Request | Scope | Access |
|---|---|---|
| `GET /api/v1/skills` (no team param) | builtin + individual layer | **L1 only**; L2 → `400 team scope required` (self-explanatory) |
| `GET /api/v1/skills?team=T` | builtin + `teams/T/skills/` | L1 = any T; L2 = T ∈ `accessibleTeams`; cross-team / unknown T → `404` (anti-probing) |

**Write contract:** `POST /api/v1/skills` `scope=team` → `teams//skills/` (L1 any / L2 own team) + `scope=deployment` → `agents/global/skills/` (L1 only); common: structure validation + content scan (upload-time, best-effort fast feedback) + audit. **Materialize-at-assign:** `PUT /workers {skills}` (live since #1212, plus the leader's existing write surface — zero new endpoints) → the controller copies `teams//skills/` → `agents//skills/` with a **mandatory content scan** (block = not copied + reported — the real gate) → worker sync loop ≤ 5min. The team layer deliberately sits *outside* `teams//shared/` (workers actively mirror `shared/` — inside it, skills would materialize to the whole team with no per-skill gate; the controller copy is the only materialization path and the only gate point).

**Boundary with assignment state:** catalog metadata (layers 1–3) · per-worker assignment state (`Worker.spec.skills`, existing worker-update API) · worker runtime copy (layer 4, not a controller concern). Update sync: list-on-read in v1 (no cache).

**Schema update (2026-09-11, #1211):** catalog entries additionally carry `version` (SKILL.md frontmatter: top-level `version`, falling back to `metadata.version`) and `requirements` (`require_bins` / `require_envs` / `require_mcps`), parsed with QwenPaw 2.2.x semantics — `metadata.{openclaw,qwenpaw,clawdbot}.requires` shadows `metadata.requires`, which shadows top-level `requires`; a bare list is shorthand for `bins`. Enforcement is runtime-dependent: the qwenpaw 2.2.x skill registry gates skill activation on these declarations; the other runtimes in `AllWorkerRuntimes` (openclaw, hermes, openhuman, deepseek-harness, copaw-legacy) have no equivalent gate yet, so a satisfied declaration is necessary but not sufficient there. The shared half of the catalog stays name-only by design (list-on-read, no per-skill object fetches); shared SKILL.md metadata is a v2 candidate.

## 11. QwenPaw version contract (#1219)

- The locked QwenPaw 2.0.1 package does **not** expose the `conflict-check` channel route (verified against the `v2.0.1` source; first appears in 2.1.1). The proxied endpoint is version-gated: documented requirement, upstream 404 passed through, client hides the entry.
- The MinIO read-back that verifies a channel write persisted is currently bounded to a few seconds, but the worker's `push_loop` (mtime-based, default 60s interval in production) may not have converged — a systematic false negative. Fix: return `pending` immediately and verify asynchronously against a `2×` push-interval deadline, logging the outcome.

## 12. Proposed sequencing

**Already landed on pending branches (no design decision needed):** #1212 — `mcpServers`/`remoteSkills` closed to default L2 + full-field policy probe test; #1211 — explicit `ActionList` on the skills resource; #1216 — dedicated `ActionWorkerApproval`, guarded-level validation, last-write-wins contract documented.

**Next, in dependency order:**

1. New PR A — **capability foundation**: `capabilities` field on the Human CR (+ validation), human-update API field (building on #1209, already approved), `hasCapability` authorizer helper, dual-layer audit client. (Depends on #1209 merging.)
2. New PR B — **MCP bearer trusted-gateway gate** (independent).
3. #1216 — `OFF` gated by `approval_policy` + dual-layer audit. (After A.)
4. #1212 — `mcpServers` restored to the L2 allowlist, action split, `remoteSkills` gated by `external_sources`, leader write scope narrowed to the non-sensitive allowlist (§5). (After A+B.)
5. #1219 — action split, secret contract, version gate, async read-back → un-draft. (After A.)
6. #1211 — data-source rework to the shared store + L1-only catalog authz (landed on the branch, review pending). The team-scoped read (`?team=`) and upload (`POST /skills`) ship with the team-skills PR tracked in #1221.

## 13. Open questions for discussion

1. **Capability value set** — is the proposed granularity right, or do you prefer a single `full_access` value (the list shape stays the same)?
2. **Trusted gateway hosts** — per-deployment config (proposed) vs a per-MCP-entry flag?
3. **Audit** — is the two-layer design (logs + append-only objects) sufficient for v1, or is a queryable audit store wanted?
4. **Skill catalog visibility** — global read for all authenticated roles (original proposal, superseded) vs team-scoped (final, 2026-09-11).
**Final (2026-09-11, issue author; supersedes the earlier view-level-only position):** scope is decided by authz, not by request shape. No param = deployment scope (builtin + individual layer), **L1 only** (L2 → `400 team scope required`, self-explanatory); `?team=T` = team scope (builtin + `teams/T/skills/`), L1 any / L2 own team, cross-team / unknown → `404`. Team-scoped storage is real (`teams//skills/` under the existing team-space STS — zero new authz infrastructure), not view-level. The workbench keeps the team picker: the Worker×skill matrix is filtered to the selected team's workers, the catalog is filtered to skills assignable to that team (catalog `runtimes` × team workers' runtimes), and a per-team "skill assets" view aggregates the team workers' `spec.skills`. The remote half (nacos registry) gets the same team-scope filter in the follow-up (P2).

---

---

# 设计:L2 权限与 capability 模型(worker 配置、频道、审批、技能目录)

关联:#1217(L2 团队范围工作区访问)· #1212(L2 范围 worker 更新)· #1216(L2 审批等级)· #1219(worker 频道 API)· #1211(技能目录 API)

按上述 PR 的 review 反馈,本 issue 汇总它们共同依赖的权限模型,供社区在实现继续前讨论。**本稿状态:维护者 review 意见已全部纳入;下文 capability 模型、secret 契约、审计设计、leader 模型为提案设计,开放讨论。**

---

## 1. 背景

L2 用户(Human CR 带 `accessibleTeams`)运营自己的团队。当前实现让每个 L2 用户通过通用更新边界触碰团队内所有 worker 的相同字段。方向保留(本团队 L2 自助),边界模型改变:

- **角色 × capability × 细粒度 Action**,替代一个通用 `ActionUpdate` 承载所有 L2 写。
- **敏感数据统一契约**(脱敏读、只写、省略=保留、审计 reveal-if-ever)。
- **技能目录的明确权威源**(不从 Worker 分配状态反推)。
- **显式的 leader(agent)模型**与**持久审计轨迹**。

存在两个权限面,刻意分开:

| 面 | 范围 | 状态 |
|---|---|---|
| **API 动作面**(本设计) | 谁能经 controller API 对哪个资源做什么 | 下文定义 |
| **房间管理面**(Matrix 权限级别,#1210) | 谁能在哪个房间改名/邀请/kick | 已定案:L1=100,L2/L3=50 |

房间 50 级不蕴含任何 API 能力,反之亦然。

## 2. 角色(不变)

| 角色 | 来源 | 团队范围 |
|---|---|---|
| admin / manager | `permissionLevel: 1` / 内置 | 全部(隐式持全部 capability) |
| L2 人类 | Human CR,`permissionLevel: 2` + `accessibleTeams` | 自己团队 |
| L3 人类 | Human CR,`permissionLevel: 3` + `accessibleWorkers` | 指定 worker——**只读**(scoped worker 的频道/审批读;不写) |
| 团队 leader(agent) | Team CR 中列为 `team_leader` 成员的 Worker CR | 本团队(见 §5) |
| worker | Worker CR | 自身(不变) |

## 3. Capability 模型

**字段。** Human CR 新增可选 `capabilities` 列表(如 `["channel_secrets"]`)。列表形态而非布尔,使值集增长不需 schema 变更;每个值是一个命名授权。

**值集(提案,v1 完整):**

| 值 | 授予 |
|---|---|
| `full_access` | 元值:蕴含其余全部。`permissionLevel: 1` 蕴含全部 capability。 |
| `channel_secrets` | 写频道凭据(§6.1) |
| `external_sources` | 新增/管理任意外部 skill 注册表及其凭据(`remoteSkills`) |
| `approval_policy` | 设置 `approval_level=OFF`(关闭 Tool Guard)——受保护档仍为默认 L2 |
| `secret_reveal` | 预留给明文 reveal 操作(§6.4)。**v1 不实现**;值已定义,保证将来落地时授权模型稳定。 |

未知值在准入时拒绝。值集在代码中定义并记录于此;增加值是增量变更(代码+文档),不是 schema 变更。

**授予/收回。** 走既有 human 更新路径(仅 admin/manager):更新体带 `capabilities` 字段,merge-patch 语义——省略=不变、显式列表=替换、显式空=清空。人类不能自助授予(self-update 保持 admin-only)。每次授予/收回进审计(§8)。

**判定顺序。** 一个 Action 放行需要:角色基线(admin/manager 恒过)**且**团队范围(目标在 `accessibleTeams` 内)**且**所需 capability(非敏感=默认 L2,敏感=对应命名值)。capability 本身从不蕴含团队范围。

**为什么是列表、为什么这个粒度。** 单个 `fullAccess` 布尔今天功能等价;列表使未来拆分(如频道凭据与审批策略分开授)成为增量加值,审计能精确记录"哪个 capability 授权了这次操作"。v1 值集覆盖 review 中识别出的全部敏感面——无占位值、无抢跑粒度。

## 4. 角色/capability 矩阵(L2 视角;admin/manager 可做一切,不列)

| 操作 | 默认 L2(本团队) | 所需 capability |
|---|:--:|---|
| 读 worker 配置 | ✅ | — |
| 更新非敏感字段:`skills`(catalog 分配:builtin + 团队层)、`mcpServers` 地址(bearer 只附加可信 gateway host,§7)、展示类字段 | ✅ | — |
| 新增/管理任意外部 skill 注册表(凭据 source URI) | — | `external_sources` |
| 读频道元数据(类型/端点/启用/展示/健康),secret 脱敏 | ✅ | — |
| 更新频道非敏感字段;重启频道 | ✅ | — |
| 写频道凭据 | — | `channel_secrets`(只写) |
| 明文 reveal 凭据 | — | `secret_reveal` + 审计(v1 无,§6.4) |
| 读审批等级;受保护档(`STRICT`/`SMART`/`AUTO`)互切 | ✅ | — |
| 设置 `approval_level=OFF` | — | `approval_policy` + 审计 |
| list 团队技能目录(`GET /skills?team=`,本团队) | ✅ | — |
| 上传技能到团队层(`POST /skills` `scope=team`) | ✅ | —(role AND scope;补偿包:扫描门 / 审计 / source+uploader 标记 / warn 通知 admin——§10) |
| 部署级(单独层)目录(`GET /skills` 无参)与 `scope=deployment` 上传 | — | 仅 L1(admin)(§10) |
| worker `env`、镜像、其他特权 spec 字段 | — | admin-only(`full_access` 也不给) |

更新请求类型中任何未显式决定的字段一律拒绝(deny-by-default,§9)。

## 5. Leader(agent)模型

两主体两条线:

**拥有该 leader 的人类。** CRD 无 per-leader "owner" 字段;"拥有"=团队范围——`accessibleTeams` 含该团队的人类可读写团队内**所有** worker,**包括 leader 本体**(leader 就是 Worker CR)。无额外机制。(若将来要排他 owner,那是新 CRD 字段,不在本设计内。)

**leader agent 对其下 worker。**"指示"= Matrix 平面(@mention / TeamHarness 派发),本设计不碰。"读写"= API 平面,设计把今天属于实现意外的状态(L2 字段白名单目前只作用于人类)钉死:

- leader 的 worker 更新**限制在与默认 L2 相同的非敏感白名单**(skills / MCP 地址)+ 既有生命周期动作(wake/sleep/ensure-ready)。全字段写(镜像、env、人设)保持 owner 级。
- leader 永不持有 capability:capability 只授予人类账号。

## 6. 敏感数据

### 6.1 分类

- **频道凭据**:`client_secret`、`app_secret`、`access_token`、`bot_token`、`password`、微信 QR session。
- **外部源凭据**:`remoteSkills` source URI 内嵌的 registry token、registry 认证块。
- **网关 key**:当前注入每个 MCP entry 的 Worker Gateway consumer key(§7)。
- **worker `env`**:可能含任意 key——保持 admin-only。

### 6.2 统一契约

1. **读返回脱敏值或 `configured: true`**——普通读路径上对任何角色都不返回明文。
2. **secret 字段只写。**
3. **更新中省略 secret = 保留原值**(nil 指针 merge-patch);显式空值=清除。
4. **明文 reveal v1 不做。** 它是系统唯一的明文出口——一旦读出,值就活在屏幕/日志/历史里,无法"没读过"。真实工作流无它也能覆盖:设置=只写、有效性=频道 health、轮换=写新值。**留门:** 若出现真实工作流,reveal 以独立鉴权+独立审计的操作落地(独立 action、`secret_reveal` capability、响应内一次性 token、逐次审计)。将来加它不需要 CRD 变更。

### 6.3 QR 凭据

设计上 ephemeral:只存在于 worker 的 qwenpaw 实例内。controller 透传 QR 与其状态轮询,不落 MinIO、不落 CR status、不落日志。钉为契约。

### 6.4 Reveal 决定(记录)

v1:无 reveal 端点。理由与未来形态记录于 §6.2(4),使该决定可被重新审议而无需重新设计。

## 7. MCP bearer 附加

`GenerateMcporterConfig` 当前对每个 MCP entry 无条件注入 `Authorization: Bearer ` 且 URL 原样使用——`mcpServers` 对 L2 可写时,指向攻击者 URL 的 entry 会外泄网关 consumer key。设计:

- **可信网关门(v1)**:bearer 只在 entry host 匹配 controller 配置的可信 gateway host 时附加;其他 entry 不带凭据写入,API 响应带警告。
- **credentialRef(长期)**:MCP entry 引用凭据而非内嵌。
- **#1212 待合分支的临时态**:`mcpServers` 暂时移出 L2 可写集合,直到门落地;上文目标态(MCP 地址编辑保持默认 L2 能力)是最终模型。

**MCP 后续项(2026-09-11,提案):** ① 部署级 MCP 目录(`GET /api/v1/mcp-servers`——受信网关 + 共享 MCP 注册表,每条目带 `timeout` / `headers` / `trusted`),镜像技能目录模式,工作台从"逐 Worker 翻"升级为"部署 MCP × Worker 矩阵";建议 PR B 之后做,`trusted` 标记是 bearer 门控的产物。② CRD:`spec.mcpServers` 条目应带 per-server 超时——qwenpaw 2.2.x worker 侧已有可配 `http_timeout`(默认 30s)而 controller 侧无对应字段,现有 dashboard 侧私有 MCP 注册表模型已有 `timeout` / `headers` 字段而 controller API 没有;该面迁入 controller API 是①的一部分。

## 8. 审计

双层,零新服务:

1. **结构化日志**(每个安全相关事件,立即)。
2. **持久 append-only 存储**:每事件一行 JSON 写入按天 MinIO 对象(如 `audit/2026-09-03.jsonl`);对象版本化可选;workbench 后续只读接入。MinIO 是现成依赖。

需审计记录的事件(who / when / 目标 / action / 授权 capability / 前后值——**永不记 secret 值**):

- capability 授予/收回
- `approval_level` 变更(`OFF` 额外记录授权 capability)
- 频道凭据写(仅字段名)
- 外部源增改(source URI 脱敏)
- (预留)secret reveal,若将来落地

## 9. 独立 Action 与 deny-by-default

scoped action 替代 L2/leader 写路径上的通用 `ActionUpdate`:

`ActionWorkerConfigUpdate`(非敏感)· `ActionExternalSourceManage`(`external_sources`)· `ActionChannelRead` · `ActionChannelUpdate` · `ActionChannelSecrets`(`channel_secrets`)· `ActionApprovalRead` · `ActionWorkerApproval`(#1216 已引入;`OFF` 另需 `approval_policy`)· `ActionSkillCatalogList`(显式;#1211 已落地)· `ActionSkillAssign`。

字段策略用白名单(请求类型增长时 deny-by-default),配反射全字段探针测试钉住覆盖——#1212 分支已落地。

## 10. 技能目录权威源(#1211)

目录由 controller 直接读 MinIO + agent 模板目录——**不**从 `Worker.spec.skills` / `spec.remoteSkills` 反推(那会把目录内容绑定到当前分配状态,并可能暴露跨团队注册表细节与凭据 source URI)。

**技能分层(定案,2026-09-11)。** 技能分四层:

1. **builtin** —— 代码捆绑的模板技能,从 deployer 的 agent 模板目录扫描(per-runtime 可用性取自 deployer 自己的模板选择函数,目录与 Worker 实际收到的内置技能永不漂移)。
2. **单独层(部署级)** —— `agents/global/skills/`(目录 `source="shared"`,值保留)+ Manager 种子技能(根级 `skills/`、`skills-alpha/`)。是 Manager/管理员的**单独**资产——**仅 L1(admin)管理**(技能 + MCP;MCP 半留在既有 manager 配置面)。Manager agent 是这一层的**被管理对象**,不是管理者。
3. **团队层** —— `teams//skills/`(team 空间根下,与 `agents//skills/` 对称);L1(全部团队)/ L2(本团队)可读写;**Manager agent 不参与团队技能任何路径**(显式声明,防未来设计把 Manager 当团队技能管理员)。
4. **worker 层** —— `agents//skills/`,物化副本层;唯一变成 prompt 内容的层。

**读契约(scope 由 authz 决定,不由请求形态决定——零部署模式检测代码):**

| 请求 | scope | 访问 |
|---|---|---|
| `GET /api/v1/skills`(无 team 参数) | builtin + 单独层 | **仅 L1**;L2 → `400 team scope required`(自解释) |
| `GET /api/v1/skills?team=T` | builtin + `teams/T/skills/` | L1 = 任意 T;L2 = T ∈ `accessibleTeams`;跨团队/未知 T → `404`(防探测) |

**写契约:** `POST /api/v1/skills` `scope=team` → `teams//skills/`(L1 任意 / L2 本团队)+ `scope=deployment` → `agents/global/skills/`(仅 L1);公共:结构校验 + 内容扫描(上传时,best-effort 快速反馈)+ 审计。**物化-at-assign:** `PUT /workers {skills}`(#1212 已 live,加 leader 既有写面——零新端点)→ controller 拷贝 `teams//skills/` → `agents//skills/`,**强制内容扫描**(block = 不拷 + 上报——真门)→ worker sync loop ≤ 5min。团队层刻意放在 `teams//shared/` **之外**(worker 主动镜像同步 `shared/`——放里面技能会无门控物化到全团队;controller 拷贝是唯一物化路径 = 唯一门控点)。

**与分配状态的边界:** catalog 元数据(1–3 层)· per-worker 分配状态(`Worker.spec.skills`,既有 worker 更新 API 管)· worker 运行时副本(第 4 层,非 controller 职责)。更新同步:v1 list-on-read(不缓存)。

**Schema 更新(2026-09-11,#1211):** 目录条目另带 `version`(SKILL.md frontmatter:顶层 `version`,兜底 `metadata.version`)与 `requirements`(`require_bins` / `require_envs` / `require_mcps`),按 QwenPaw 2.2.x 语义解析——`metadata.{openclaw,qwenpaw,clawdbot}.requires` 遮蔽 `metadata.requires`,后者遮蔽顶层 `requires`;裸列表 = `bins` 简写。执行是运行时相关的:qwenpaw 2.2.x 技能注册表以这些声明门控技能启用;`AllWorkerRuntimes` 中的其他运行时(openclaw、hermes、openhuman、deepseek-harness、copaw-legacy)尚无等价门控,声明满足是必要非充分条件。目录的共享半侧按设计保持仅名(list-on-read,不逐技能取对象);共享技能的 SKILL.md 元数据是 v2 候选。

## 11. QwenPaw 版本契约(#1219)

- 锁定的 QwenPaw 2.0.1 包**没有** `conflict-check` 频道路由(对 `v2.0.1` 源码核实;2.1.1 才出现)。代理端点做版本门:文档标注要求、上游 404 透传、客户端隐藏入口。
- 验证频道写已持久化的 MinIO 回读当前窗口仅数秒,而 worker `push_loop`(mtime 检测,生产默认 60s 周期)可能尚未收敛——系统性假阴性。修法:立即返回 `pending`,异步按 `2×` 推送周期截止验证,结果记日志。

## 12. 落地顺序

**待合分支已落地(不需设计决定):** #1212——`mcpServers`/`remoteSkills` 对默认 L2 关闭 + 全字段策略探针测试;#1211——skills 资源显式 `ActionList`;#1216——专属 `ActionWorkerApproval`、受保护档校验、last-write-wins 契约文档化。

**后续,按依赖序:**

1. 新 PR A——**capability 地基**:Human CR `capabilities` 字段(+校验)、human 更新 API 字段(基于已获批的 #1209)、`hasCapability` authorizer 助手、双层审计 client。(依赖 #1209 合并。)
2. 新 PR B——**MCP bearer 可信网关门**(独立)。
3. #1216——`OFF` 加 `approval_policy` 门 + 双层审计。(A 后。)
4. #1212——`mcpServers` 放回 L2 白名单、Action 拆分、`remoteSkills` 加 `external_sources` 门、leader 写面收窄到非敏感白名单(§5)。(A+B 后。)
5. #1219——Action 拆分、secret 契约、版本门、异步回读 → 脱 draft。(A 后。)
6. #1211——数据源改造到共享库 + L1-only 目录鉴权(分支已落地,待 review)。团队范围读(`?team=`)与上传(`POST /skills`)随 #1221 跟踪的 team-skills PR 交付。

## 13. 开放问题(社区讨论)

1. **capability 值集**——提案粒度合适吗,还是倾向单一 `full_access` 值(列表形态不变)?
2. **可信 gateway host**——按部署配置(提案)vs 按 MCP entry 标记?
3. **审计**——双层(日志 + append-only 对象)v1 够用吗,还是要可查询审计存储?
4. **技能目录可见性**——全体认证角色全局读(原提案,已废)vs 团队 scope 制(定案,2026-09-11)。
**定案(2026-09-11,issue 提出者;取代早前"仅视图层"立场):** scope 由 authz 决定,不由请求形态决定。无参 = 部署 scope(builtin + 单独层),**仅 L1**(L2 → `400 team scope required`,自解释);`?team=T` = 团队 scope(builtin + `teams/T/skills/`),L1 任意 / L2 本团队,跨团队/未知 → `404`。团队级存储是真实的(`teams//skills/`,既有 team 空间 STS——零新 authz 基建),不是视图层。工作台保留团队选择器:Worker×技能矩阵按选中团队的 Worker 过滤;目录按"该团队可分配"过滤(目录 `runtimes` × 团队 Worker 的运行时);"团队技能资产"视图聚合该团队 Worker 的 `spec.skills`。远程半侧(nacos 注册表)的同样团队 scope 过滤在后续跟进(P2)。

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.