fix(security): remediate 2026-05-12 audit findings (Stages 2–5)
Remediation of the 2026-05-12/13 audits (78 findings + cluster gaps), tracked in deploy-k3s/SECURITY.md, plus fixes from two independent post-remediation reviews. Auth & sessions: - SHA-256 hashed auth-token storage (C1); prior-token cache eviction on re-login (MEDIUM-1) - local Google JWKS verification, iss/aud/exp checks (C2/C3) - constant-time login + generic errors (L1/LIVE-L11/LIVE-L13) - per-account login lockout keyed on distinct source IPs (M5/MEDIUM-3) - verified-email gating, login rate limiting (LIVE-L19, H1-H3) IAP & webhooks: - Apple/Google cross-account replay protection (C5/C6/C10/C13, H5/H6) - migrations 000003-000006 (token hashing, IAP replay, audit_log + webhook_event_log table creation, append-only audit log) Authorization & races: - file-ownership owner-OR-member fix (C7), atomic share-code join (C9/H9), device-token reassignment (C8/LOW-3) Secrets & deploy: - secrets file-mounted at /etc/honeydue/secrets, not env (F8); Redis password out of the ConfigMap (HIGH-1); B2 keys reconciled - digest-pinned images, admin ingress hardening, CSP/HSTS, /metrics lockdown; kubeconfig 0600, etcd secrets-encryption, fail2ban + unattended-upgrades at provision; secret-rotation runbook Build, vet, and the full test suite (incl. -race) pass; the goose migration chain is verified against PostgreSQL 16. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,9 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FileOwnershipService checks whether a user owns a file referenced by URL.
|
||||
// It queries task completion images, document files, and document images
|
||||
// to determine ownership through residence access.
|
||||
// FileOwnershipService checks whether a user has access to a file referenced
|
||||
// by URL. It queries task completion images, document files, and document
|
||||
// images, resolving access through residence ownership or membership.
|
||||
type FileOwnershipService struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
@@ -17,16 +17,31 @@ func NewFileOwnershipService(db *gorm.DB) *FileOwnershipService {
|
||||
return &FileOwnershipService{db: db}
|
||||
}
|
||||
|
||||
// IsFileOwnedByUser checks if the given file URL belongs to a record
|
||||
// that the user has access to (via residence membership).
|
||||
// accessibleResidenceIDs returns a subquery of residence IDs the user can
|
||||
// access: residences they own (residence_residence.owner_id) UNION residences
|
||||
// they are a member of (residence_residence_users).
|
||||
//
|
||||
// Audit C7: the previous queries joined residence_residence_users only, so a
|
||||
// residence owner who was not also a member of the join table could not pass
|
||||
// the ownership check for files in their own property.
|
||||
func (s *FileOwnershipService) accessibleResidenceIDs(userID uint) *gorm.DB {
|
||||
return s.db.Raw(`
|
||||
SELECT id FROM residence_residence WHERE owner_id = ?
|
||||
UNION
|
||||
SELECT residence_id FROM residence_residence_users WHERE user_id = ?
|
||||
`, userID, userID)
|
||||
}
|
||||
|
||||
// IsFileOwnedByUser checks if the given file URL belongs to a record in a
|
||||
// residence the user owns or is a member of.
|
||||
func (s *FileOwnershipService) IsFileOwnedByUser(fileURL string, userID uint) (bool, error) {
|
||||
// Check task completion images: image_url -> completion -> task -> residence -> user access
|
||||
// Task completion images: image_url -> completion -> task -> residence.
|
||||
var completionImageCount int64
|
||||
err := s.db.Model(&models.TaskCompletionImage{}).
|
||||
Joins("JOIN task_taskcompletion ON task_taskcompletion.id = task_taskcompletionimage.completion_id").
|
||||
Joins("JOIN task_task ON task_task.id = task_taskcompletion.task_id").
|
||||
Joins("JOIN residence_residence_users ON residence_residence_users.residence_id = task_task.residence_id").
|
||||
Where("task_taskcompletionimage.image_url = ? AND residence_residence_users.user_id = ?", fileURL, userID).
|
||||
Where("task_taskcompletionimage.image_url = ?", fileURL).
|
||||
Where("task_task.residence_id IN (?)", s.accessibleResidenceIDs(userID)).
|
||||
Count(&completionImageCount).Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -35,11 +50,11 @@ func (s *FileOwnershipService) IsFileOwnedByUser(fileURL string, userID uint) (b
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check document files: file_url -> document -> residence -> user access
|
||||
// Document files: file_url -> document -> residence.
|
||||
var documentCount int64
|
||||
err = s.db.Model(&models.Document{}).
|
||||
Joins("JOIN residence_residence_users ON residence_residence_users.residence_id = task_document.residence_id").
|
||||
Where("task_document.file_url = ? AND residence_residence_users.user_id = ?", fileURL, userID).
|
||||
Where("task_document.file_url = ?", fileURL).
|
||||
Where("task_document.residence_id IN (?)", s.accessibleResidenceIDs(userID)).
|
||||
Count(&documentCount).Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -48,12 +63,12 @@ func (s *FileOwnershipService) IsFileOwnedByUser(fileURL string, userID uint) (b
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check document images: image_url -> document_image -> document -> residence -> user access
|
||||
// Document images: image_url -> document_image -> document -> residence.
|
||||
var documentImageCount int64
|
||||
err = s.db.Model(&models.DocumentImage{}).
|
||||
Joins("JOIN task_document ON task_document.id = task_documentimage.document_id").
|
||||
Joins("JOIN residence_residence_users ON residence_residence_users.residence_id = task_document.residence_id").
|
||||
Where("task_documentimage.image_url = ? AND residence_residence_users.user_id = ?", fileURL, userID).
|
||||
Where("task_documentimage.image_url = ?", fileURL).
|
||||
Where("task_document.residence_id IN (?)", s.accessibleResidenceIDs(userID)).
|
||||
Count(&documentImageCount).Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
Reference in New Issue
Block a user