176 lines
4.5 KiB
Markdown
176 lines
4.5 KiB
Markdown
# Security Fixes Applied to FRE-683
|
|
|
|
**Date**: 2026-04-27
|
|
**Status**: HIGH & MEDIUM priority fixes completed
|
|
|
|
## Summary
|
|
|
|
All security issues identified in the Security Review have been addressed:
|
|
|
|
### HIGH Priority Fixes ✅
|
|
|
|
#### 1. Path Traversal Vulnerability (CVE-class)
|
|
**File**: `internal/attachment/manager.go`
|
|
|
|
**Changes**:
|
|
- Added `isAttachmentIDSafe()` function to validate attachmentID contains only safe characters (alphanumeric, hyphen, underscore)
|
|
- Added `sanitizeAttachmentID()` function that:
|
|
- Validates attachmentID using `isAttachmentIDSafe()`
|
|
- Uses `filepath.Clean()` to resolve any `..` or `.` components
|
|
- Rejects any ID that resolves differently after cleaning
|
|
- All attachment operations now call `sanitizeAttachmentID()` before use:
|
|
- `Download()`
|
|
- `Upload()`
|
|
- `Get()`
|
|
- `Delete()`
|
|
|
|
**Code Example**:
|
|
```go
|
|
func isAttachmentIDSafe(id string) bool {
|
|
if id == "" {
|
|
return false
|
|
}
|
|
// Only allow alphanumeric, hyphen, and underscore
|
|
for _, r := range id {
|
|
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
|
|
(r >= '0' && r <= '9') || r == '-' || r == '_') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
```
|
|
|
|
#### 2. No File Size Limit (DoS)
|
|
**File**: `internal/attachment/manager.go`
|
|
|
|
**Changes**:
|
|
- Added `maxUploadSize = 50 * 1024 * 1024` constant (50MB)
|
|
- Modified `Upload()` to use `io.LimitReader()` before reading
|
|
- Prevents memory exhaustion from large uploads
|
|
|
|
**Code Example**:
|
|
```go
|
|
const maxUploadSize = 50 * 1024 * 1024 // 50MB
|
|
|
|
func (m *AttachmentManager) Upload(attachmentID, name string, reader io.Reader) error {
|
|
// Sanitize attachmentID to prevent path traversal
|
|
if sanitizedID, err := sanitizeAttachmentID(attachmentID); err != nil {
|
|
return err
|
|
} else {
|
|
attachmentID = sanitizedID
|
|
}
|
|
|
|
if err := os.MkdirAll(m.attachmentsDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Limit reader to maxUploadSize to prevent DoS
|
|
limitedReader := io.LimitReader(reader, maxUploadSize)
|
|
data, err := io.ReadAll(limitedReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(filepath.Join(m.attachmentsDir, attachmentID), data, 0644)
|
|
}
|
|
```
|
|
|
|
### MEDIUM Priority Fixes ✅
|
|
|
|
#### 3. Contact Edit Overwrites
|
|
**File**: `cmd/contacts.go`
|
|
|
|
**Changes**:
|
|
- Modified `contactEditCmd()` to check `cmd.Flags().Changed()` before setting pointer fields
|
|
- Prevents overwriting existing values when flags are not explicitly provided
|
|
|
|
**Before** (Buggy):
|
|
```go
|
|
req := contact.UpdateContactRequest{
|
|
Name: &name,
|
|
Phone: &phone,
|
|
Address: &address,
|
|
Notes: ¬es,
|
|
}
|
|
```
|
|
|
|
**After** (Fixed):
|
|
```go
|
|
req := contact.UpdateContactRequest{}
|
|
|
|
if cmd.Flags().Changed("name") {
|
|
req.Name = &name
|
|
}
|
|
if cmd.Flags().Changed("phone") {
|
|
req.Phone = &phone
|
|
}
|
|
if cmd.Flags().Changed("address") {
|
|
req.Address = &address
|
|
}
|
|
if cmd.Flags().Changed("notes") {
|
|
req.Notes = ¬es
|
|
}
|
|
```
|
|
|
|
#### 4. No Concurrency Protection
|
|
**File**: `internal/contact/manager.go`
|
|
|
|
**Changes**:
|
|
- Added `sync.Mutex` field to `ContactManager` struct
|
|
- Wrapped all CRUD operations with `m.mu.Lock()` / `defer m.mu.Unlock()`:
|
|
- `List()`
|
|
- `Create()`
|
|
- `Get()`
|
|
- `Update()`
|
|
- `Delete()`
|
|
|
|
**Code Example**:
|
|
```go
|
|
type ContactManager struct {
|
|
mu sync.Mutex
|
|
configDir string
|
|
contactsFile string
|
|
}
|
|
|
|
func (m *ContactManager) List(req ListContactsRequest) (*ListContactsResponse, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
contacts, err := m.loadContacts()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// ... rest of function
|
|
}
|
|
```
|
|
|
|
### Pending / Not Required ✅
|
|
|
|
#### 5. Inconsistent Path Resolution
|
|
**Status**: Not fixed - this is a minor consistency issue, not a security vulnerability
|
|
|
|
**Analysis**:
|
|
- `internal/attachment/manager.go` uses `os.Getenv("HOME")` - acceptable for local tool
|
|
- `internal/contact/manager.go` uses `config.NewConfigManager().ConfigDir()` - recommended approach
|
|
- Both resolve to the same location (`~/.config/pop/`)
|
|
- This is a code style preference, not a bug
|
|
|
|
---
|
|
|
|
## Verification
|
|
|
|
All changes have been applied to the source files. The Code Reviewer should:
|
|
1. Review the security fixes in `internal/attachment/manager.go`
|
|
2. Review the concurrency fixes in `internal/contact/manager.go`
|
|
3. Review the edit command fix in `cmd/contacts.go`
|
|
4. Approve for merge once verified
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. ✅ Security fixes applied
|
|
2. ⏳ Assign to Code Reviewer for re-review
|
|
3. ⏳ Re-assign to Founding Engineer upon approval
|
|
4. ⏳ Merge and deploy
|