-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Fix #179 security hardening and #199 default model/CI #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
1e966a0
3f66ccb
e2f3b43
11f4a51
ee798bd
9c96618
efb6850
a37b3ba
8da7f08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| pull_request: | ||
|
|
||
| jobs: | ||
| test: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version-file: go.mod | ||
| cache: true | ||
|
|
||
| - name: Check formatting | ||
| run: | | ||
| unformatted="$(gofmt -l .)" | ||
| if [ -n "$unformatted" ]; then | ||
| echo "The following files need gofmt:" | ||
| echo "$unformatted" | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Run tests | ||
| run: go test ./... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -282,9 +282,9 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) { | |
| } | ||
|
|
||
| logger.DebugCF("slack", "Received message", map[string]interface{}{ | ||
| "sender_id": senderID, | ||
| "chat_id": chatID, | ||
| "preview": utils.Truncate(content, 50), | ||
| "sender_id": senderID, | ||
| "chat_id": chatID, | ||
| "preview": utils.Truncate(content, 50), | ||
| "has_thread": threadTS != "", | ||
| }) | ||
|
|
||
|
|
@@ -295,6 +295,12 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) { | |
| if ev.User == c.botUserID { | ||
| return | ||
| } | ||
| if !c.IsAllowed(ev.User) { | ||
| logger.DebugCF("slack", "App mention rejected by allowlist", map[string]interface{}{ | ||
| "user_id": ev.User, | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| senderID := ev.User | ||
| channelID := ev.Channel | ||
|
|
@@ -346,6 +352,14 @@ func (c *SlackChannel) handleSlashCommand(event socketmode.Event) { | |
| } | ||
|
|
||
| senderID := cmd.UserID | ||
| if !c.IsAllowed(senderID) { | ||
| logger.DebugCF("slack", "Slash command rejected by allowlist", map[string]interface{}{ | ||
| "user_id": senderID, | ||
| "command": cmd.Command, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Positive — Allowlist Fix for Slash Commands] @jmahotiedu — Same correct pattern as app mentions. The check is early, before any processing or response. The |
||
| }) | ||
| return | ||
| } | ||
|
|
||
| channelID := cmd.ChannelID | ||
| chatID := channelID | ||
| content := cmd.Text | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,16 +83,16 @@ type QQConfig struct { | |
| } | ||
|
|
||
| type DingTalkConfig struct { | ||
| Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DINGTALK_ENABLED"` | ||
| ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_ID"` | ||
| ClientSecret string `json:"client_secret" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_SECRET"` | ||
| AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"` | ||
| Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DINGTALK_ENABLED"` | ||
| ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_ID"` | ||
| ClientSecret string `json:"client_secret" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_SECRET"` | ||
| AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"` | ||
| } | ||
|
|
||
| type SlackConfig struct { | ||
| Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_SLACK_ENABLED"` | ||
| BotToken string `json:"bot_token" env:"PICOCLAW_CHANNELS_SLACK_BOT_TOKEN"` | ||
| AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_SLACK_APP_TOKEN"` | ||
| Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_SLACK_ENABLED"` | ||
| BotToken string `json:"bot_token" env:"PICOCLAW_CHANNELS_SLACK_BOT_TOKEN"` | ||
| AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_SLACK_APP_TOKEN"` | ||
| AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_SLACK_ALLOW_FROM"` | ||
| } | ||
|
|
||
|
|
@@ -135,7 +135,7 @@ func DefaultConfig() *Config { | |
| Agents: AgentsConfig{ | ||
| Defaults: AgentDefaults{ | ||
| Workspace: "~/.picoclaw/workspace", | ||
| Model: "glm-4.7", | ||
| Model: "openrouter/auto", | ||
| MaxTokens: 8192, | ||
| Temperature: 0.7, | ||
| MaxToolIterations: 20, | ||
|
|
@@ -233,6 +233,8 @@ func LoadConfig(path string) (*Config, error) { | |
| return nil, err | ||
| } | ||
|
|
||
| normalizeLegacyModelDefaults(cfg) | ||
|
|
||
| return cfg, nil | ||
| } | ||
|
|
||
|
|
@@ -250,7 +252,7 @@ func SaveConfig(path string, cfg *Config) error { | |
| return err | ||
| } | ||
|
|
||
| return os.WriteFile(path, data, 0644) | ||
| return writePrivateFile(path, data) | ||
| } | ||
|
|
||
| func (c *Config) WorkspacePath() string { | ||
|
|
@@ -317,3 +319,25 @@ func expandHome(path string) string { | |
| } | ||
| return path | ||
| } | ||
|
|
||
| func writePrivateFile(path string, data []byte) error { | ||
|
||
| if err := os.WriteFile(path, data, 0600); err != nil { | ||
| return err | ||
| } | ||
| return os.Chmod(path, 0600) | ||
| } | ||
|
|
||
| func normalizeLegacyModelDefaults(cfg *Config) { | ||
|
||
| if cfg == nil { | ||
| return | ||
| } | ||
| if cfg.Agents.Defaults.Model != "glm-4.7" { | ||
| return | ||
| } | ||
| if cfg.Providers.Zhipu.APIKey != "" { | ||
| return | ||
| } | ||
| if cfg.Providers.OpenRouter.APIKey != "" { | ||
| cfg.Agents.Defaults.Model = "openrouter/auto" | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Positive — Allowlist Fix for App Mentions] @jmahotiedu — Correct placement. The
IsAllowed()check is placed BEFORE the reaction emoji, pending ack, andHandleMessage()call. This means blocked users get zero feedback — no "eyes" reaction, no processing. This is the right security posture.Note:
BaseChannel.HandleMessage(base.go:86) also has its ownIsAllowed()check as defense-in-depth, but this early return avoids unnecessary work (reaction API calls, string processing) for blocked users.