Resolves bd-ee1: Add security tests for WriteFile permissions in doctor command Added comprehensive security tests for the FixGitignore function to verify: - Files are created with 0600 permissions (secure, owner-only read/write) - Existing files with insecure permissions are fixed - Read-only files can be updated (permissions fixed first) - File ownership is correct - Permissions are enforced even on systems that respect umask Also improved FixGitignore implementation to: - Handle read-only files by fixing permissions before writing - Explicitly set permissions after write to ensure 0600 regardless of umask - Maintain secure permissions throughout the operation Tests verify the gosec G306 security concern is properly addressed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package doctor
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// GitignoreTemplate is the canonical .beads/.gitignore content
|
|
const GitignoreTemplate = `# SQLite databases
|
|
*.db
|
|
*.db?*
|
|
*.db-journal
|
|
*.db-wal
|
|
*.db-shm
|
|
|
|
# Daemon runtime files
|
|
daemon.lock
|
|
daemon.log
|
|
daemon.pid
|
|
bd.sock
|
|
|
|
# Legacy database files
|
|
db.sqlite
|
|
bd.db
|
|
|
|
# Merge artifacts (temporary files from 3-way merge)
|
|
beads.base.jsonl
|
|
beads.base.meta.json
|
|
beads.left.jsonl
|
|
beads.left.meta.json
|
|
beads.right.jsonl
|
|
beads.right.meta.json
|
|
|
|
# Keep JSONL exports and config (source of truth for git)
|
|
!issues.jsonl
|
|
!metadata.json
|
|
!config.json
|
|
`
|
|
|
|
// requiredPatterns are patterns that MUST be in .beads/.gitignore
|
|
var requiredPatterns = []string{
|
|
"beads.base.jsonl",
|
|
"beads.left.jsonl",
|
|
"beads.right.jsonl",
|
|
"beads.base.meta.json",
|
|
"beads.left.meta.json",
|
|
"beads.right.meta.json",
|
|
"*.db?*",
|
|
}
|
|
|
|
// CheckGitignore checks if .beads/.gitignore is up to date
|
|
func CheckGitignore() DoctorCheck {
|
|
gitignorePath := filepath.Join(".beads", ".gitignore")
|
|
|
|
// Check if file exists
|
|
content, err := os.ReadFile(gitignorePath) // #nosec G304 -- path is hardcoded
|
|
if err != nil {
|
|
return DoctorCheck{
|
|
Name: "Gitignore",
|
|
Status: "warning",
|
|
Message: ".beads/.gitignore not found",
|
|
Fix: "Run: bd init (safe to re-run) or bd doctor --fix",
|
|
}
|
|
}
|
|
|
|
// Check for required patterns
|
|
contentStr := string(content)
|
|
var missing []string
|
|
for _, pattern := range requiredPatterns {
|
|
if !strings.Contains(contentStr, pattern) {
|
|
missing = append(missing, pattern)
|
|
}
|
|
}
|
|
|
|
if len(missing) > 0 {
|
|
return DoctorCheck{
|
|
Name: "Gitignore",
|
|
Status: "warning",
|
|
Message: "Outdated .beads/.gitignore (missing merge artifact patterns)",
|
|
Detail: "Missing: " + strings.Join(missing, ", "),
|
|
Fix: "Run: bd doctor --fix or bd init (safe to re-run)",
|
|
}
|
|
}
|
|
|
|
return DoctorCheck{
|
|
Name: "Gitignore",
|
|
Status: "ok",
|
|
Message: "Up to date",
|
|
}
|
|
}
|
|
|
|
// FixGitignore updates .beads/.gitignore to the current template
|
|
func FixGitignore() error {
|
|
gitignorePath := filepath.Join(".beads", ".gitignore")
|
|
|
|
// If file exists and is read-only, fix permissions first
|
|
if info, err := os.Stat(gitignorePath); err == nil {
|
|
if info.Mode().Perm()&0200 == 0 { // No write permission for owner
|
|
if err := os.Chmod(gitignorePath, 0600); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write canonical template with secure file permissions
|
|
if err := os.WriteFile(gitignorePath, []byte(GitignoreTemplate), 0600); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure permissions are set correctly (some systems respect umask)
|
|
if err := os.Chmod(gitignorePath, 0600); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|