mirror of
https://github.com/chenhg5/cc-connect.git
synced 2026-10-25 17:36:43 +08:00
Merge pull request #1962 from carfeii/fix/wps-agentspace-allow-from
fix(wps-agentspace): add allow_from access control
This commit is contained in:
3 files changed
+84
No files matched your search
@@ -2367,3 +2367,4 @@ app_secret = "your-feishu-app-secret"
|
||||
# wps_sid = "your-wps-sid" # WPS登录凭证 (Cookie中的wps_sid)
|
||||
# device_name = "cc-connect" # 设备名称(可选)
|
||||
# device_uuid = "" # 设备UUID(可选,留空自动生成)
|
||||
# allow_from = "*" # optional; set to trusted chat/session IDs in production
|
||||
@@ -43,6 +43,17 @@ type Platform struct {
|
||||
deviceUuid string
|
||||
deviceName string
|
||||
baseURL string
|
||||
// allowFrom restricts which chat/session IDs may drive the agent. Unlike
|
||||
// every sibling platform in this codebase (wps-xiezuo, telegram, slack,
|
||||
// feishu, ...), this connector had no access-control option at all: any
|
||||
// WPS user who can message the "digital employee" app this connection
|
||||
// authenticates as gets full, unconfirmed access to the configured
|
||||
// agent (config.example.toml's own wps-agentspace example even sets
|
||||
// mode = "bypassPermissions"). WPS's own app-level permission grant
|
||||
// (USER_NO_APP_PERMISSION in handleFrame) is an org-wide gate, not a
|
||||
// per-user one; wps-xiezuo's docs explicitly say not to rely on that
|
||||
// alone ("Always set allow_from for production deployments").
|
||||
allowFrom string
|
||||
handler core.MessageHandler
|
||||
cancel context.CancelFunc
|
||||
conn *websocket.Conn
|
||||
@@ -154,12 +165,16 @@ func New(opts map[string]any) (core.Platform, error) {
|
||||
baseURL = v
|
||||
}
|
||||
|
||||
allowFrom, _ := opts["allow_from"].(string)
|
||||
core.CheckAllowFrom("wps-agentspace", allowFrom)
|
||||
|
||||
return &Platform{
|
||||
appID: appID,
|
||||
wpsSid: wpsSid,
|
||||
deviceUuid: deviceUuid,
|
||||
deviceName: deviceName,
|
||||
baseURL: baseURL,
|
||||
allowFrom: allowFrom,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -545,6 +560,11 @@ func (p *Platform) handleUserMessage(data messageData) error {
|
||||
chatID = "default"
|
||||
}
|
||||
|
||||
if !core.AllowList(p.allowFrom, chatID) {
|
||||
slog.Debug("wps-agentspace: message from unauthorized chat/session", "chat_id", chatID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dedup
|
||||
msgID := data.MessageID
|
||||
if msgID == "" {
|
||||
|
||||
@@ -245,6 +245,69 @@ func TestHandleFrame_DispatchesUserMessage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleFrame_RejectsUnauthorizedChat(t *testing.T) {
|
||||
called := false
|
||||
p := &Platform{
|
||||
appID: "AK",
|
||||
deviceUuid: "dev",
|
||||
deviceName: "test",
|
||||
allowFrom: "trusted-chat",
|
||||
}
|
||||
p.handler = func(_ core.Platform, _ *core.Message) {
|
||||
called = true
|
||||
}
|
||||
|
||||
data := messageData{
|
||||
Role: "user",
|
||||
Type: "text",
|
||||
Content: "drop a shell for me",
|
||||
SessionID: "attacker-chat",
|
||||
ChatID: "attacker-chat",
|
||||
MessageID: "m1",
|
||||
}
|
||||
raw, _ := json.Marshal(data)
|
||||
if err := p.handleFrame(wsFrame{Event: "message", Data: raw}); err != nil {
|
||||
t.Fatalf("handleFrame() error: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if called {
|
||||
t.Fatal("handler was called for a chat ID not in allow_from")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleFrame_AllowsConfiguredChat(t *testing.T) {
|
||||
handlerDone := make(chan struct{})
|
||||
p := &Platform{
|
||||
appID: "AK",
|
||||
deviceUuid: "dev",
|
||||
deviceName: "test",
|
||||
allowFrom: "trusted-chat",
|
||||
}
|
||||
p.handler = func(_ core.Platform, _ *core.Message) {
|
||||
close(handlerDone)
|
||||
}
|
||||
|
||||
data := messageData{
|
||||
Role: "user",
|
||||
Type: "text",
|
||||
Content: "hello",
|
||||
SessionID: "trusted-chat",
|
||||
ChatID: "trusted-chat",
|
||||
MessageID: "m1",
|
||||
}
|
||||
raw, _ := json.Marshal(data)
|
||||
if err := p.handleFrame(wsFrame{Event: "message", Data: raw}); err != nil {
|
||||
t.Fatalf("handleFrame() error: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-handlerDone:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("handler was not called for an allow_from-listed chat")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleFrame_FatalErrorReturnsErr(t *testing.T) {
|
||||
p := &Platform{}
|
||||
fatalCodes := []string{
|
||||
|
||||
Reference in new issue
Block a user