Files
nezha_domains/cmd/dashboard/controller/mcp_capability.go
T
naibaandcloudcode e8dabf5bc6 feat(auth): add PAT auth, scoped REST/MCP access, CSRF, and tenant isolation
Introduce Personal Access Tokens (nzp_*) as a stateless auth path alongside
JWT, gated per-endpoint by a scope middleware (nezha:{resource}:{verb}) with
fail-closed empty-scope defaults and a server-id whitelist. Self-management
endpoints (profile, api-tokens, oauth2 bind, refresh-token) explicitly reject
PATs to block privilege-escalation chains. A revoke registry tears down active
long-lived connections (terminal, fm, ws, transfer, mcp) the moment a PAT is
deleted, with a tombstone closing the revoke->register race.

Add an MCP endpoint that proxies tool calls (exec, fs read/write/delete,
transfer) to agents over gRPC, guarded by origin/DNS-rebinding checks, a
per-token rate limiter, audit logging, and a kill switch. Serialize all
sends through the IOStream wrapper to honour grpc-go's concurrency contract.

Add CSRF double-submit protection on unsafe cookie-authenticated methods,
exempting authenticated PAT requests by context identity (not a forgeable
Authorization header). Apply visibility/whitelist filtering consistently
across list, get-by-id, and mutate paths to enforce tenant isolation.

Migrate legacy mcp:* scopes: rewrite read/exec to nezha:* equivalents and
drop dangerous write/delete/wildcard grants.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-30 15:56:44 +00:00

75 lines
1.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controller
import (
"strings"
"github.com/nezhahq/nezha/model"
)
// MCPMinAgentVersion 是支持 MCP 的最低 agent 版本。
//
// release 流程:在 agent ship 了 MCP handlers 后,把此值更新为该 release 的版本号。
// 不变量:必须为非空。否则旧 agent 收到 TaskTypeExec/TaskTypeFs* 等新任务类型时
// 走 default 分支不回 TaskResultdashboard 要等 CallAgent 超时(30s)甚至更久
// fs.transfer 的 IOStream attach 30s)才能感知,这是 server-transfer 已经
// 通过 MinServerTransferAgentVersion 修复过的同类问题。
const MCPMinAgentVersion = "v2.1.0"
// requireAgentSupportsMCP 在 tool handler 调 CallAgent 之前快速失败不支持的 agent。
// 仅作为 UX 优化:真正的安全/正确性由 agent 端 task switch 的 default 分支保障。
func requireAgentSupportsMCP(server *model.Server) error {
if MCPMinAgentVersion == "" || server == nil || server.Host == nil {
return nil
}
if compareSemver(server.Host.Version, MCPMinAgentVersion) < 0 {
return errMCPUnsupported
}
return nil
}
// compareSemver 比较两个 "MAJOR.MINOR.PATCH[-suffix]" 字符串。
// 返回 -1/0/1。无法解析时按字符串字典序比较,保证全序但可能不精确——
// 对于 "agent 太老" 的快速失败用途已经足够。
func compareSemver(a, b string) int {
if a == b {
return 0
}
aparts := semverParts(a)
bparts := semverParts(b)
for i := 0; i < 3; i++ {
if aparts[i] < bparts[i] {
return -1
}
if aparts[i] > bparts[i] {
return 1
}
}
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
func semverParts(v string) [3]int {
v = strings.TrimPrefix(v, "v")
if i := strings.IndexAny(v, "-+"); i >= 0 {
v = v[:i]
}
var out [3]int
parts := strings.Split(v, ".")
for i := 0; i < 3 && i < len(parts); i++ {
n := 0
for _, c := range parts[i] {
if c < '0' || c > '9' {
break
}
n = n*10 + int(c-'0')
}
out[i] = n
}
return out
}