Fix #179 security hardening and #199 default model/CI#211
Fix #179 security hardening and #199 default model/CI#211jmahotiedu wants to merge 9 commits intosipeed:mainfrom
Conversation
|
@Zepan This PR addresses security hardening (fixing #179) and default model/CI improvements (#199). The roadmap explicitly calls for Security Audit & Bug Fixes as volunteer role #3. Recommendation: Review and merge. Security fixes should be prioritized — the roadmap document acknowledges early "vibe coding" created security gaps. |
|
@jmahotiedu take look into the conflicts |
Leeaandrob
left a comment
There was a problem hiding this comment.
PR #211 Deep Review — @jmahotiedu
Excellent work, @jmahotiedu. This is a well-structured security PR that addresses real vulnerabilities from issue #179 with proper test coverage. The migration strategy for the default model is smart — gracefully handles existing users' configs at load time.
Verification
- Checked out branch locally:
fix/issues-179-199-security-model go vet ./pkg/channels/... ./pkg/config/... ./pkg/cron/... ./pkg/auth/... ./pkg/migrate/...— cleango teston all 5 modified packages — all 62 tests passinggo test ./pkg/channels/... -race— clean, no data races- Confirmed
handleAppMention,handleSlashCommand, andhandleMessageEventALL now haveIsAllowed()checks - Confirmed
openrouter/autois correctly routed viastrings.HasPrefix(model, "openrouter/")at http_provider.go:203 - Confirmed
writePrivateFilecorrectly handles both new files (via 0600 perm) AND existing files with wrong perms (via explicitos.Chmod) - Audited issue #179 — items 1 (file perms) and 2 (Slack allowlist) are fixed; item 3 (cron exec restriction) appears to have been refactored away in current code architecture
Summary of Findings
MEDIUM (Suggestions, not blocking)
- M1:
writePrivateFileis duplicated identically inconfig.go:323andcron/service.go:460— DRY violation. Move topkg/utils/. - M2:
normalizeLegacyModelDefaults— no test for the edge case whereglm-4.7is set but NEITHER Zhipu nor OpenRouter key exists (model staysglm-4.7with no usable provider). Add a test to document this intentional behavior. - M3: CI workflow could benefit from
go vet ./...step (catches common Go issues beyond formatting) and-raceflag ongo test.
LOW
- L1: Merge conflicts with current upstream/main are expected (Chinese/Japanese READMEs don't exist on this branch's base). Already flagged.
POSITIVE
- Slack allowlist bypass fixed —
handleAppMention(line 298) andhandleSlashCommand(line 355) now enforceIsAllowed()BEFORE any processing (reactions, acks, HandleMessage). Correct placement. - File permissions hardened to 0600 — config.json and cron store both contain API keys/tokens. 0644 was a real leak vector on shared systems.
- Smart migration —
normalizeLegacyModelDefaultsat config load time gracefully migrates existing users without breaking Zhipu users who intentionally useglm-4.7. - Test coverage — every change has corresponding tests: allowlist rejection for app mentions and slash commands, file permission verification, config normalization (both OpenRouter migration AND Zhipu preservation).
- Cross-platform test awareness —
runtime.GOOS == "windows"skip for permission tests;setTestHomehelper for HOME env across Linux/Mac/Windows. - CI workflow — clean gofmt + go test,
go-version-file: go.modfor version consistency. os.Chmodafteros.WriteFileis correct —WriteFiledoesn't change permissions on existing files, so the chmod is necessary for files that already exist with 0644.
Verdict: APPROVE
Solid security hardening with comprehensive tests. The medium items are suggestions for improvement, not blockers. Ready to merge after resolving upstream merge conflicts.
| if ev.User == c.botUserID { | ||
| return | ||
| } | ||
| if !c.IsAllowed(ev.User) { |
There was a problem hiding this comment.
[Positive — Allowlist Fix for App Mentions] @jmahotiedu — Correct placement. The IsAllowed() check is placed BEFORE the reaction emoji, pending ack, and HandleMessage() 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 own IsAllowed() check as defense-in-depth, but this early return avoids unnecessary work (reaction API calls, string processing) for blocked users.
| if !c.IsAllowed(senderID) { | ||
| logger.DebugCF("slack", "Slash command rejected by allowlist", map[string]interface{}{ | ||
| "user_id": senderID, | ||
| "command": cmd.Command, |
There was a problem hiding this comment.
[Positive — Allowlist Fix for Slash Commands] @jmahotiedu — Same correct pattern as app mentions. The check is early, before any processing or response. The cmd.Command in the debug log is a good touch for audit trails.
pkg/config/config.go
Outdated
| return os.Chmod(path, 0600) | ||
| } | ||
|
|
||
| func normalizeLegacyModelDefaults(cfg *Config) { |
There was a problem hiding this comment.
[Positive + M2 Suggestion] @jmahotiedu — Smart migration function. The logic flow is clear:
- Not
glm-4.7? → leave it alone (user chose something deliberately) - Has Zhipu key? → leave it (they intend to use Zhipu)
- Has OpenRouter key? → migrate to
openrouter/auto - Neither key? → do nothing (model stays
glm-4.7)
Case 4 is intentionally conservative (we cannot guess the user's intended provider), but it should have a test to document this behavior:
func TestLoadConfigKeepsGLMWhenNoKeysConfigured(t *testing.T) {
// ... config with model=glm-4.7, no API keys
// Assert model is still glm-4.7
}There was a problem hiding this comment.
Addressed in e2f3b43: added TestLoadConfigKeepsLegacyGLMModelWhenNoProviderKeysConfigured in pkg/config/config_test.go to document the no-provider-keys glm-4.7 behavior.
pkg/config/config.go
Outdated
| return path | ||
| } | ||
|
|
||
| func writePrivateFile(path string, data []byte) error { |
There was a problem hiding this comment.
[M1 — MEDIUM: DRY Violation] @jmahotiedu — This exact function is duplicated in pkg/cron/service.go:460. Both are identical:
func writePrivateFile(path string, data []byte) error {
if err := os.WriteFile(path, data, 0600); err != nil {
return err
}
return os.Chmod(path, 0600)
}Consider moving it to pkg/utils/ (which already exists with media.go and string.go) and importing from both packages. Not blocking, but worth cleaning up.
There was a problem hiding this comment.
Addressed in e2f3b43: moved the duplicated helper to pkg/utils/file.go as utils.WritePrivateFile and switched both config/cron call sites to use it.
|
@jmahotiedu take look on it |
… coverage Move duplicated writePrivateFile logic into pkg/utils.WritePrivateFile, reuse it from config and cron, add the glm-4.7 no-provider-keys test, and extend CI with go vet plus race-enabled tests.
|
Addressed the review suggestions in follow-up commit e2f3b43:
Local verification run:
Note: go test -race could not be executed in my local Windows environment because CGO is disabled by default there; race coverage is now enforced in CI on Linux. |
Resolve config/cron merge conflicts while preserving security hardening and follow-up review fixes: shared private-file writer, glm legacy-model normalization tests, and CI vet/race checks.
|
Follow-up: merged upstream main into this branch in 11f4a51, resolved the remaining conflicts, and preserved the review-fix changes (utils.WritePrivateFile, legacy-model normalization tests, and CI vet/race checks). The PR is now mergeable again. |
Run go generate ./cmd/picoclaw on CI so //go:embed workspace assets exist before go vet and race tests.
|
Addressed the CI failure in ee798bd by generating embedded workspace assets before vet/tests (go generate ./cmd/picoclaw), so go vet ./... no longer fails on missing workspace embed files in clean CI checkouts. |
|
Conflicts are resolved and PR #211 is now mergeable ( What was done:
Local verification:
Fresh CI is running for this head:
If these complete green, please merge ASAP. |
|
Update: latest head
PR is conflict-free and mergeable ( |
|
@Huaaudio @Leeaandrob quick status update for The WeCom race-test failure is fixed on the current head (
GitHub now shows this PR as blocked on review ( |
Summary