Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,95 @@
## [v0.11.0] - 2026-02-25

### 🔐 Slack 安全增强与可靠性提升

This release addresses critical security and reliability gaps identified in the [Slack Gap Analysis Report](docs/chatapps/slack-gap-analysis.md). We've implemented comprehensive path traversal protection, Socket Mode ACK retry mechanism, and extensive documentation.

### Added
- **Path Traversal Attack Protection**:
- New `expandPath()` function with `~` expansion to user home directory
- New `isSensitivePath()` function blocking access to system directories (`/etc`, `/var`, `/usr`, `/bin`, `/sbin`, `/root`, `/proc`, `/sys`, `/boot`, `/dev`)
- Automatic detection and blocking of path traversal attempts (e.g., `../etc/passwd`)
- Safe path cleaning with `filepath.Clean` for relative paths

- **Socket Mode ACK Retry Mechanism**:
- New `sendACKWithRetry()` function with exponential backoff (1s → 2s → 4s)
- Maximum 3 retries (4 total attempts) for reliable message delivery
- Slack API compliant 3-second response requirement
- Comprehensive logging for debugging connection issues

- **Comprehensive Unit Tests**:
- 26 test cases for `expandPath()` covering normal paths, edge cases, and security scenarios
- 10 test cases for `isSensitivePath()` covering all blocked directories
- 93.8% test coverage for path handling functions
- New test file `chatapps/setup_test.go` (+279 lines)

- **Gap Analysis Report** ([Issue #21](https://github.com/hrygo/hotplex/issues/21)):
- Comprehensive 416-line comparison: HotPlex vs OpenClaw Slack implementations
- 30+ feature gaps identified across 6 categories (P0/P1/P2 priority)
- 3-phase implementation roadmap (14-20 weeks estimated)
- Technical debt risk identification

- **Documentation Updates**:
- System prompt configuration guide with injection flow diagram
- Security features documentation (path checks, ACK retry, signature verification)
- Troubleshooting examples (Q5: System prompt not生效,Q6: Path blocked)
- Example environment files (`.env.development`, `.env.production`)

### Changed
- **Configuration Enhancements** (`chatapps/configs/slack.yaml`):
- Detailed path security documentation with examples
- ACK retry mechanism explanation
- System prompt injection flow description
- Complete troubleshooting section

- **User Manual** (`docs/chatapps/chatapps-slack.md`):
- Added Chapter 7: System Prompt Configuration
- Added Chapter 8: Security Features
- Updated changelog with v0.10.0, v0.9.0, v0.8.0

- **Code Quality**:
- Project-wide lint cleanup
- Improved error handling in path expansion
- Enhanced logging for security events

### Fixed
- **Duplicate Message Processing** ([PR #23](https://github.com/hrygo/hotplex/pull/23)):
- Removed duplicate `handleEventsAPI()` call in Socket Mode
- Added empty payload validation
- Fixed potential message duplication issue

- **Security Vulnerabilities**:
- Blocked access to sensitive system directories
- Prevented path traversal attacks via `..` sequences
- Hardened path validation with multiple security layers

### Technical Details
- **Files Changed**: 7 files
- **Lines Added**: +1,256
- **Lines Removed**: -140
- **Net Change**: +1,116 lines
- **Test Coverage**: 93.8%+ (41 test cases)

### Verification
```bash
✅ go test ./... - All tests pass
✅ go build ./... - Build succeeds
✅ golangci-lint run - 0 issues
✅ Path security - Blocks /etc, /var, /root successfully
✅ ACK retry - Handles connection failures
```

### Contributors
- [@hrygo](https://github.com/hrygo)

### Related
- **PR**: [#23](https://github.com/hrygo/hotplex/pull/23)
- **Issue**: [#21](https://github.com/hrygo/hotplex/issues/21)
- **Release**: [v0.11.0](https://github.com/hrygo/hotplex/releases/tag/v0.11.0)
- **Gap Analysis**: [docs/chatapps/slack-gap-analysis.md](docs/chatapps/slack-gap-analysis.md)

---

## [v0.10.0] - 2026-02-23

### 🚀 ChatApps-as-a-Service Milestone (v0.10.0)
Expand Down
8 changes: 8 additions & 0 deletions chatapps/base/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ type RichContent struct {
Blocks []any
Embeds []any
Attachments []Attachment
Reactions []Reaction
}

// Reaction represents a reaction to add to a message
type Reaction struct {
Name string // emoji name (e.g., "thumbsup", "+1")
Channel string
Timestamp string // message timestamp to react to
}

type Attachment struct {
Expand Down
65 changes: 65 additions & 0 deletions chatapps/slack/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ func (a *Adapter) defaultSender(ctx context.Context, sessionID string, msg *base
}
}

// Send reactions if present
if msg.RichContent != nil && len(msg.RichContent.Reactions) > 0 {
for _, reaction := range msg.RichContent.Reactions {
reaction.Channel = channelID
if err := a.AddReaction(ctx, reaction); err != nil {
a.Logger().Error("Failed to add reaction", "error", err, "reaction", reaction.Name)
}
}
}

// Send media/attachments if present
if msg.RichContent != nil && len(msg.RichContent.Attachments) > 0 {
for _, attachment := range msg.RichContent.Attachments {
Expand Down Expand Up @@ -530,3 +540,58 @@ func (a *Adapter) sendToChannelOnce(ctx context.Context, channelID, text, thread
a.Logger().Debug("Message sent successfully", "channel", channelID)
return nil
}

// AddReaction adds a reaction to a message
func (a *Adapter) AddReaction(ctx context.Context, reaction base.Reaction) error {
if a.config.BotToken == "" {
return fmt.Errorf("slack bot token not configured")
}

if reaction.Channel == "" || reaction.Timestamp == "" {
return fmt.Errorf("channel and timestamp are required for reaction")
}

payload := map[string]any{
"channel": reaction.Channel,
"name": reaction.Name,
"ts": reaction.Timestamp,
}

body, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("marshal payload: %w", err)
}

req, err := http.NewRequestWithContext(ctx, "POST", "https://slack.com/api/reactions.add", bytes.NewReader(body))
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+a.config.BotToken)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("send request: %w", err)
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode >= 400 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("reaction add failed: %d %s", resp.StatusCode, string(respBody))
}

var slackResp struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
}
if err := json.NewDecoder(resp.Body).Decode(&slackResp); err != nil {
return fmt.Errorf("parse response: %w", err)
}

if !slackResp.OK {
return fmt.Errorf("slack API error: %s", slackResp.Error)
}

a.Logger().Debug("Reaction added", "emoji", reaction.Name, "channel", reaction.Channel)
return nil
}
4 changes: 3 additions & 1 deletion docs-site/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export default defineConfig({
{ text: 'WebSocket Protocol', link: '/guide/websocket' },
{ text: 'OpenCode HTTP/SSE', link: '/guide/opencode-http' },
{ text: 'ChatApps Overview', link: '/guide/chatapps' },
{ text: '└─ DingTalk Deep Dive', link: '/guide/chatapps-dingtalk' }
{ text: '└─ DingTalk Deep Dive', link: '/guide/chatapps-dingtalk' },
{ text: '└─ Slack Deep Dive', link: '/guide/chatapps-slack' },
{ text: '└─ Slack Gap Analysis', link: '/guide/slack-gap-analysis' }
]
},
{
Expand Down