Skip to content
Closed
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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ build-all: generate
GOOS=windows GOARCH=amd64 $(GO) build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)
@echo "All builds complete"

## build-riscv64: Build picoclaw for RISC-V 64-bit
build-riscv64: generate
@mkdir -p $(BUILD_DIR)
GOOS=linux GOARCH=riscv64 $(GO) build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64 ./$(CMD_DIR)
@echo "RISC-V build complete: $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64"

## install: Install picoclaw to system and copy builtin skills
install: build
@echo "Installing $(BINARY_NAME)..."
Expand Down
42 changes: 42 additions & 0 deletions cmd/picoclaw/cmd_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,47 @@ func statusCmd() {
fmt.Printf(" %s (%s): %s\n", provider, cred.AuthMethod, status)
}
}

// Display enabled channels
fmt.Println("\nChannels:")
if cfg.Channels.WebSocket.Enabled {
fmt.Printf(" WebSocket: ✓ http://%s:%d\n", cfg.Channels.WebSocket.Host, cfg.Channels.WebSocket.Port)
}
if cfg.Channels.Telegram.Enabled {
fmt.Println(" Telegram: ✓")
}
if cfg.Channels.Discord.Enabled {
fmt.Println(" Discord: ✓")
}
if cfg.Channels.Slack.Enabled {
fmt.Println(" Slack: ✓")
}
if cfg.Channels.QQ.Enabled {
fmt.Println(" QQ: ✓")
}
if cfg.Channels.Feishu.Enabled {
fmt.Println(" Feishu: ✓")
}
if cfg.Channels.DingTalk.Enabled {
fmt.Println(" DingTalk: ✓")
}
if cfg.Channels.LINE.Enabled {
fmt.Println(" LINE: ✓")
}
if cfg.Channels.WeCom.Enabled {
fmt.Println(" WeCom: ✓")
}
if cfg.Channels.WeComApp.Enabled {
fmt.Println(" WeCom App: ✓")
}
if cfg.Channels.WhatsApp.Enabled {
fmt.Println(" WhatsApp: ✓")
}
if cfg.Channels.OneBot.Enabled {
fmt.Println(" OneBot: ✓")
}
if cfg.Channels.MaixCam.Enabled {
fmt.Println(" MaixCam: ✓")
}
}
}
7 changes: 7 additions & 0 deletions config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@
"webhook_path": "/webhook/wecom-app",
"allow_from": [],
"reply_timeout": 5
},
"websocket": {
"enabled": false,
"host": "127.0.0.1",
"port": 8080,
"token": "YOUR_TOKEN",
"allow_from": []
}
},
"providers": {
Expand Down
13 changes: 13 additions & 0 deletions pkg/channels/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,19 @@ func (m *Manager) initChannels() error {
}
}

if m.config.Channels.WebSocket.Enabled {
logger.DebugC("channels", "Attempting to initialize WebSocket channel")
websocket, err := NewWebSocketChannel(m.config.Channels.WebSocket, m.bus)
if err != nil {
logger.ErrorCF("channels", "Failed to initialize WebSocket channel", map[string]any{
"error": err.Error(),
})
} else {
m.channels["websocket"] = websocket
logger.InfoC("channels", "WebSocket channel enabled successfully")
}
}

logger.InfoCF("channels", "Channel initialization completed", map[string]any{
"enabled_channels": len(m.channels),
})
Expand Down
146 changes: 146 additions & 0 deletions pkg/channels/websocket/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# WebSocket Chat Security - XSS Protection

## Issue
XSS vulnerability via markdown rendering where LLM responses containing HTML could be rendered directly without sanitization.

## Solution
Implemented comprehensive XSS protection using DOMPurify library with strict configuration.

## Implementation Details

### 1. DOMPurify Integration
- **Library**: DOMPurify v3.0.8 (loaded from CDN)
- **Location**: `/pkg/channels/websocket/chat.html`

### 2. Security Configuration
```javascript
const DOMPURIFY_CONFIG = {
ALLOWED_TAGS: [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'p', 'br', 'hr',
'strong', 'em', 'b', 'i', 'u', 'del', 's', 'code', 'pre',
'ul', 'ol', 'li',
'blockquote',
'a', 'img',
'table', 'thead', 'tbody', 'tr', 'th', 'td',
'span', 'div'
],
ALLOWED_ATTR: [
'href', 'title', 'alt', 'src',
'class', 'id'
],
ALLOW_DATA_ATTR: false,
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form', 'input', 'button'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur'],
KEEP_CONTENT: true
};
```

### 3. Security Features

#### Blocked Elements
- `<script>` tags - Prevents JavaScript execution
- `<style>` tags - Prevents CSS injection
- `<iframe>`, `<object>`, `<embed>` - Prevents content embedding attacks
- `<form>`, `<input>`, `<button>` - Prevents form injection

#### Blocked Attributes
All event handler attributes are blocked:
- `onerror`, `onload`, `onclick`
- `onmouseover`, `onfocus`, `onblur`
- And any other `on*` attributes

#### Allowed Safe Elements
Only safe HTML tags for markdown rendering:
- Headings: `h1-h6`
- Text formatting: `p`, `br`, `hr`, `strong`, `em`, `b`, `i`, `u`, `del`, `s`
- Code: `code`, `pre`
- Lists: `ul`, `ol`, `li`
- Quotes: `blockquote`
- Links and images: `a`, `img`
- Tables: `table`, `thead`, `tbody`, `tr`, `th`, `td`
- Containers: `span`, `div`

### 4. Implementation Points

All markdown rendering goes through the `sanitizeHtml()` function:

1. **Assistant Messages** (Line 1345-1348)
```javascript
if (sender === 'assistant' || sender === 'system') {
const rawHtml = marked.parse(content);
const cleanHtml = sanitizeHtml(rawHtml);
contentDiv.innerHTML = cleanHtml;
}
```

2. **System Messages Update** (Line 1368-1371)
```javascript
function updateReconnectMessage(newContent) {
const rawHtml = marked.parse(newContent);
const cleanHtml = sanitizeHtml(rawHtml);
contentDiv.innerHTML = cleanHtml;
}
```

3. **Retry Button Messages** (Line 1393-1396)
```javascript
const rawHtml = marked.parse(
t.clickToRetry + '\n\n**[' + t.retryButton + ']**'
);
const cleanHtml = sanitizeHtml(rawHtml);
contentDiv.innerHTML = cleanHtml;
```

### 5. User Input Protection
User messages are rendered as plain text using `textContent` instead of `innerHTML`:
```javascript
} else {
// 用户消息保持纯文本
contentDiv.textContent = content;
}
```

## Testing

A test suite is provided in `xss_test.html` that validates protection against:
- Script tag injection
- Event handler injection (onclick, onerror, etc.)
- iframe/object/embed injection
- JavaScript protocol URLs
- SVG-based attacks
- Style injection
- Form injection
- And various bypass attempts

To run tests:
1. Open `xss_test.html` in a browser
2. All tests should pass
3. Check browser console for any JavaScript errors

## Attack Vectors Mitigated

✅ Direct script injection: `<script>alert('XSS')</script>`
✅ Image onerror: `<img src="x" onerror="alert('XSS')">`
✅ JavaScript URLs: `[link](javascript:alert('XSS'))`
✅ Event handlers: `<a onclick="alert('XSS')">Click</a>`
✅ SVG attacks: `<svg onload="alert('XSS')"></svg>`
✅ Iframe injection: `<iframe src="javascript:alert('XSS')"></iframe>`
✅ Object tags: `<object data="javascript:alert('XSS')"></object>`
✅ Form injection: `<form><input type="text"></form>`
✅ Style injection: `<style>body { display: none; }</style>`

## Maintenance

When updating the chat interface:
1. Always use `sanitizeHtml()` for any HTML content from external sources
2. Never use `innerHTML` directly with user/LLM content
3. Keep DOMPurify library updated to latest stable version
4. Review `DOMPURIFY_CONFIG` when adding new markdown features

## References

- [DOMPurify GitHub](https://github.com/cure53/DOMPurify)
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [Marked.js Documentation](https://marked.js.org/)
Loading