security fixes and structural improvements

This commit is contained in:
2026-01-11 21:31:34 +00:00
parent dc44a89a67
commit 010b36789c
4 changed files with 219 additions and 5 deletions

44
main.go
View File

@@ -211,7 +211,14 @@ func (f *sqlFileBuffer) Read(p []byte) (n int, err error) { return 0
func (f *sqlFileBuffer) Seek(offset int64, whence int) (int64, error) { return 0, nil }
func (f *sqlFileBuffer) Readdir(count int) ([]os.FileInfo, error) { return nil, os.ErrInvalid }
func (f *sqlFileBuffer) Stat() (os.FileInfo, error) { return &memFileInfo{name: f.filename}, nil }
func (f *sqlFileBuffer) Write(p []byte) (int, error) { return f.buffer.Write(p) }
func (f *sqlFileBuffer) Write(p []byte) (int, error) {
// Limit calendar file size to 10MB to prevent DoS
const maxSize = 10 * 1024 * 1024
if f.buffer.Len()+len(p) > maxSize {
return 0, errors.New("file size exceeds maximum allowed (10MB)")
}
return f.buffer.Write(p)
}
func (f *sqlFileBuffer) Close() error {
// Flush buffer to DB
_, err := db.Exec(`
@@ -247,6 +254,12 @@ func publicCalendarHandler(w http.ResponseWriter, r *http.Request) {
}
userID := parts[0]
// Add security headers
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("Referrer-Policy", "no-referrer")
var content []byte
err := db.QueryRow("SELECT content FROM calendars WHERE user_id = ?", userID).Scan(&content)
if err != nil {
@@ -258,6 +271,16 @@ func publicCalendarHandler(w http.ResponseWriter, r *http.Request) {
w.Write(content)
}
func securityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("Referrer-Policy", "no-referrer")
next.ServeHTTP(w, r)
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
@@ -267,6 +290,13 @@ func authMiddleware(next http.Handler) http.Handler {
return
}
// Input validation for username
if len(user) > 255 || len(user) == 0 {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var id, hash string
err := db.QueryRow("SELECT id, password_hash FROM users WHERE username = ?", user).Scan(&id, &hash)
if err != nil || bcrypt.CompareHashAndPassword([]byte(hash), []byte(pass)) != nil {
@@ -286,7 +316,10 @@ func generatePassword() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
b := make([]byte, 16)
for i := range b {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
log.Fatalf("Failed to generate secure random password: %v", err)
}
b[i] = charset[num.Int64()]
}
return string(b)
@@ -318,6 +351,11 @@ func runREPL() {
username := ""
if len(parts) > 1 {
username = parts[1]
// Validate username length and characters
if len(username) > 255 || len(username) == 0 {
fmt.Println("Error: username must be 1-255 characters")
break
}
} else {
u, _ := uuid.NewV7()
username = u.String()
@@ -440,7 +478,7 @@ func main() {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/webdav/") {
// Strip prefix for WebDAV handler so it sees relative root
http.StripPrefix("/webdav", authMiddleware(davHandler)).ServeHTTP(w, r)
http.StripPrefix("/webdav", securityHeadersMiddleware(authMiddleware(davHandler))).ServeHTTP(w, r)
return
}
// Public Calendar