Add security headers and configurable rate limiting middleware

Change-Id: Id976e75e58d79ea38e7939afe7576a2295a1c5af
diff --git a/cmd/koralmapper/main.go b/cmd/koralmapper/main.go
index c1fbabe..9fc6f5d 100644
--- a/cmd/koralmapper/main.go
+++ b/cmd/koralmapper/main.go
@@ -20,6 +20,7 @@
 	"github.com/KorAP/Koral-Mapper/mapper"
 	"github.com/alecthomas/kong"
 	"github.com/gofiber/fiber/v2"
+	"github.com/gofiber/fiber/v2/middleware/limiter"
 	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 )
@@ -309,6 +310,30 @@
 	configTmpl := template.Must(template.ParseFS(staticFS, "static/config.html"))
 	pluginTmpl := template.Must(template.ParseFS(staticFS, "static/plugin.html"))
 
+	// Security headers middleware to mitigate MIME-sniffing and referrer
+	// information leaks (OWASP Secure Headers). X-Frame-Options is
+	// intentionally omitted because the service is designed to be embedded
+	// in cross-origin iframes (Kalamar plugin).
+	app.Use(func(c *fiber.Ctx) error {
+		c.Set("X-Content-Type-Options", "nosniff")
+		c.Set("Referrer-Policy", "strict-origin-when-cross-origin")
+		return c.Next()
+	})
+
+	// Rate limiting middleware to prevent resource exhaustion from
+	// request floods. The maximum number of requests per minute
+	// per IP is configurable via the "rateLimit" YAML key or the
+	// KORAL_MAPPER_RATE_LIMIT environment variable (default: 100).
+	rateLimit := yamlConfig.RateLimit
+	if rateLimit <= 0 {
+		rateLimit = 100
+	}
+	app.Use(limiter.New(limiter.Config{
+		Max:               rateLimit,
+		Expiration:        1 * time.Minute,
+		LimiterMiddleware: limiter.SlidingWindow{},
+	}))
+
 	// Health check endpoint
 	app.Get("/health", func(c *fiber.Ctx) error {
 		return c.SendString("OK")