fix: final vulnerability sweep — storage auth, security headers, body limits, cookie flags
- Storage route moved behind auth middleware (was publicly accessible) - Security headers: X-Content-Type-Options, X-Frame-Options, CSP, Referrer-Policy - Request body size limit: 10 MB on all endpoints via MaxBytesReader - Session cookie now sets Secure flag when BASE_URL uses HTTPS - readFile() returns proper errors for dirs & oversized files (was nil,nil) - Removed dead DEEPSEEK_API_KEY code from main.go - Added fmt import to ai/receipt.go for error formatting
This commit is contained in:
parent
e831fcf617
commit
a7381bde0c
3 changed files with 29 additions and 9 deletions
|
|
@ -9,6 +9,7 @@
|
|||
package ai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -72,10 +73,10 @@ func readFile(path string) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("readFile: %q is a directory, not a file", path)
|
||||
}
|
||||
if info.Size() > 10<<20 {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("readFile: %q exceeds 10 MB limit", path)
|
||||
}
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -199,13 +200,15 @@ func (h *AuthHandler) VerifyOTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Set the HTTP-only session cookie with a 24-hour TTL.
|
||||
// Set the session cookie (HttpOnly, SameSite=Lax, Secure, 24h).
|
||||
secure := strings.HasPrefix(os.Getenv("BASE_URL"), "https://")
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session_token",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: secure,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
})
|
||||
|
||||
|
|
|
|||
28
main.go
28
main.go
|
|
@ -50,9 +50,6 @@ func main() {
|
|||
smtpUser := os.Getenv("SMTP_USER")
|
||||
smtpPass := os.Getenv("SMTP_PASS")
|
||||
|
||||
// DeepSeek API key is read directly by the ai package.
|
||||
_ = os.Getenv("DEEPSEEK_API_KEY")
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Database
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -109,6 +106,25 @@ func main() {
|
|||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.RealIP)
|
||||
// Request body size limit on all endpoints (10 MB).
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
// Security headers.
|
||||
r.Use(func(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("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
w.Header().Set("Content-Security-Policy",
|
||||
"default-src 'self'; img-src 'self' data:; script-src 'self' https://unpkg.com; style-src 'self' 'unsafe-inline'")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
// PWA headers for service worker.
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
|
|
@ -147,9 +163,6 @@ func main() {
|
|||
http.ServeFile(w, r, "static/favicon.svg")
|
||||
}))
|
||||
|
||||
// Serve uploaded receipt images.
|
||||
r.Get("/storage/*", http.StripPrefix("/storage/", http.FileServer(http.Dir("storage"))).ServeHTTP)
|
||||
|
||||
// ---- Public routes (no auth required) ----
|
||||
|
||||
r.Get("/", authHandler.LandingPage)
|
||||
|
|
@ -179,6 +192,9 @@ func main() {
|
|||
|
||||
// Filing.
|
||||
r.Post("/events/{id}/file", fileHandler.FileEvent)
|
||||
|
||||
// Storage (receipt images) — protected by auth middleware.
|
||||
r.Get("/storage/*", http.StripPrefix("/storage/", http.FileServer(http.Dir("storage"))).ServeHTTP)
|
||||
})
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue