Fix 8 high-severity audit findings across security, Go, API, and container domains

- Use typed context key for auth context to prevent collisions (auth.go)
- Eliminate nested locking in CheckRateLimit to prevent potential deadlock (auth.go)
- Add 1MB request body size limit middleware to prevent DoS (router.go)
- Return proper gRPC status codes instead of nil errors on failures (handler.go)
- Use key name instead of raw API key in admin URL paths to prevent secret leakage (keys.go, router.go, keystore_db.go, keystore_hybrid.go)
- Enforce RBAC authorization in service Send/SendBatch for both REST and gRPC (service.go)
- Pin runtime Docker image to alpine:3.21 for reproducible builds (Dockerfile)
- Enable readOnlyRootFilesystem with /tmp emptyDir in k8s deployment (deployment.yaml)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 20:17:51 -07:00
parent 71b02758d7
commit 298c960808
9 changed files with 212 additions and 43 deletions
+42 -6
View File
@@ -276,6 +276,16 @@ func (s *NotificationService) processNotification(ctx context.Context, msg *doma
// Send queues a notification for delivery
func (s *NotificationService) Send(ctx context.Context, notification *domain.Notification) (*domain.NotificationResult, error) {
// Enforce RBAC authorization if configured
if err := s.checkAuthorization(ctx, notification); err != nil {
return &domain.NotificationResult{
NotificationID: notification.ID,
Success: false,
Error: err.Error(),
SentAt: time.Now(),
}, err
}
// Store the notification
s.storeNotification(notification)
@@ -301,6 +311,13 @@ func (s *NotificationService) Send(ctx context.Context, notification *domain.Not
func (s *NotificationService) SendBatch(ctx context.Context, notifications []*domain.Notification) ([]*domain.NotificationResult, error) {
results := make([]*domain.NotificationResult, 0, len(notifications))
// Enforce RBAC authorization for each notification
for _, notification := range notifications {
if err := s.checkAuthorization(ctx, notification); err != nil {
return nil, fmt.Errorf("authorization denied for notification type=%s account=%s: %w", notification.Type, notification.Account, err)
}
}
// Store all notifications
for _, notification := range notifications {
s.storeNotification(notification)
@@ -439,12 +456,7 @@ func (s *NotificationService) GetStats(ctx context.Context) (*domain.Notificatio
// GetNotifiers returns information about available notifiers, filtered by authorization if auth context is provided
func (s *NotificationService) GetNotifiers(ctx context.Context) (*domain.NotifiersResponse, error) {
// Extract auth context from request context if available
var authCtx *auth.AuthContext
if authVal := ctx.Value("auth"); authVal != nil {
if ac, ok := authVal.(*auth.AuthContext); ok {
authCtx = ac
}
}
authCtx, _ := auth.GetAuthContext(ctx)
supportedTypes := s.factory.SupportedTypes()
notifiers := make([]domain.NotifierInfo, 0, len(supportedTypes))
@@ -510,6 +522,30 @@ func (s *NotificationService) updateNotification(notification *domain.Notificati
s.notifications[notification.ID] = notification
}
// checkAuthorization verifies that the caller is authorized to send to the given notifier/account.
// Returns nil if authorized or if RBAC is not configured.
func (s *NotificationService) checkAuthorization(ctx context.Context, notification *domain.Notification) error {
if s.authz == nil || !s.authz.HasRules() {
return nil // RBAC not configured
}
authCtx, ok := auth.GetAuthContext(ctx)
if !ok {
return nil // No auth context (auth may be disabled)
}
account := notification.Account
if account == "" && s.accountResolver != nil {
account = s.accountResolver.GetDefaultAccount(notification.Type)
}
if !s.authz.IsAuthorized(authCtx, notification.Type, account) {
return fmt.Errorf("not authorized to send %s notifications to account %s", notification.Type, account)
}
return nil
}
// matchesFilter checks if a notification matches the filter
func (s *NotificationService) matchesFilter(notification *domain.Notification, filter *domain.NotificationFilter) bool {
if filter == nil {