fix(sec): Authorization-Bypässe und Path-Traversal schließen, Xapian-Doku bereinigen
- SEC: requireMailAccess auf GET /api/threads/{threadID} — superadmin/domain_admin konnten Mail-Metadaten lesen
- SEC: requireMailAccess auf POST /api/export/ediscovery — superadmin/domain_admin konnten bis zu 10k EML exportieren
- SEC: V1-API user-role Keys müssen 'contact=' angeben — verhindert vollständige Tenant-Enumeration
- SEC: Domain-Regex-Validierung in handleCertACME vor filepath.Join und certbot-Aufruf
- docs: README und config.test.yml auf Manticore Search aktualisiert (kein Xapian mehr)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,10 +17,13 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var validDomainRE = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`)
|
||||
|
||||
const (
|
||||
certDir = "/etc/ssl/archivmail"
|
||||
certPath = "/etc/ssl/archivmail/archivmail.crt"
|
||||
@@ -270,6 +273,10 @@ func (s *Server) handleCertACME(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "email is required")
|
||||
return
|
||||
}
|
||||
if !validDomainRE.MatchString(req.Domain) {
|
||||
writeError(w, http.StatusBadRequest, "invalid domain name")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify certbot is available before attempting anything.
|
||||
certbotPath, err := exec.LookPath("certbot")
|
||||
|
||||
@@ -213,7 +213,7 @@ func (s *Server) routes() {
|
||||
s.mux.HandleFunc("GET /api/admin/storage/stats", s.authAdmin(s.handleStorageStats))
|
||||
s.mux.HandleFunc("GET /api/mails/{id}", s.auth(s.requireMailAccess(s.handleGetMail)))
|
||||
s.mux.HandleFunc("GET /api/mails/{id}/attachments/{index}", s.auth(s.requireMailAccess(s.handleGetAttachment)))
|
||||
s.mux.HandleFunc("GET /api/threads/{threadID}", s.auth(s.handleGetThread))
|
||||
s.mux.HandleFunc("GET /api/threads/{threadID}", s.auth(s.requireMailAccess(s.handleGetThread)))
|
||||
s.mux.HandleFunc("GET /api/mails/{id}/raw", s.auth(s.requireMailAccess(s.handleGetRaw)))
|
||||
// PROJ-44: OCR-Text-Download — gleicher ACL-Pfad wie /raw.
|
||||
s.mux.HandleFunc("GET /api/mails/{id}/ocr-text", s.auth(s.requireMailAccess(s.handleGetOCRText)))
|
||||
@@ -256,7 +256,7 @@ func (s *Server) routes() {
|
||||
// Export routes
|
||||
s.mux.HandleFunc("GET /api/export/pdf/{id}", s.auth(s.requireMailAccess(s.handleExportPDF)))
|
||||
s.mux.HandleFunc("POST /api/export/zip", s.auth(s.requireMailAccess(s.handleExportZIP)))
|
||||
s.mux.HandleFunc("POST /api/export/ediscovery", s.auth(s.handleExportEDiscovery))
|
||||
s.mux.HandleFunc("POST /api/export/ediscovery", s.auth(s.requireMailAccess(s.handleExportEDiscovery)))
|
||||
|
||||
// Upload routes (admin only)
|
||||
s.mux.HandleFunc("POST /api/admin/upload", s.authAdmin(s.handleUpload))
|
||||
|
||||
@@ -64,6 +64,12 @@ func (s *Server) handleV1SearchMails(w http.ResponseWriter, r *http.Request) {
|
||||
Page: page,
|
||||
}
|
||||
|
||||
// User-role keys must always scope their search to a specific contact address.
|
||||
if akSess.Role == "user" && contactFilter == "" {
|
||||
writeError(w, http.StatusBadRequest, "user-role API keys require the 'contact' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
// "contact" searches both From and To fields via OwnEmail.
|
||||
if contactFilter != "" {
|
||||
req.OwnEmail = contactFilter
|
||||
@@ -153,13 +159,6 @@ func (s *Server) handleV1SearchMails(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
m.HasAttachments = len(pm.Attachments) > 0
|
||||
|
||||
// Role-based filtering: "user" role only sees mails they are involved in.
|
||||
if akSess.Role == "user" {
|
||||
// User keys need a contact filter or the mail must belong to the tenant.
|
||||
// For user-role keys without explicit contact filter, we still return
|
||||
// all tenant mails (tenant isolation is handled by the index).
|
||||
}
|
||||
|
||||
mails = append(mails, m)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user