towerops/lib/towerops_web/plugs/security_headers.ex
Graham McIntie 34fe5d7e49 Security fixes: mask credentials in logs/API, fix cookie/CSP/LIKE injection/webhooks
CRITICAL fixes:
- Mask SNMP community string in agent channel logs (CRITICAL-1)
- Remove snmpv3 passwords from REST API responses, return _set booleans (CRITICAL-2)
- Replace snmp_community with snmp_community_set in GraphQL type (CRITICAL-3)

HIGH fixes:
- Fix cookie same_site from invalid 'Towerops' to 'Lax' (HIGH-4)
- Remove unsafe-eval from CSP script-src (HIGH-6)
- Block GraphQL introspection queries in production (HIGH-7)
- Sanitize LIKE wildcards in SNMP device name search (HIGH-8)
- Reject webhooks when no secret configured instead of accepting (HIGH-9)

MEDIUM fixes:
- Hash mobile session tokens (SHA-256) before DB storage (MEDIUM-10)
- Apply security headers in all environments, not just prod (MEDIUM-14)
- Add GraphQL query complexity limit (500) in production (MEDIUM-16)
- Fix X-Frame-Options to DENY to match frame-ancestors 'none' (MEDIUM-13)
2026-02-15 09:09:04 -06:00

40 lines
1.2 KiB
Elixir

defmodule ToweropsWeb.Plugs.SecurityHeaders do
@moduledoc """
Adds security headers to all responses.
Headers added:
- Content-Security-Policy: Restricts resource loading to prevent XSS
- X-Frame-Options: Prevents clickjacking attacks
- X-Content-Type-Options: Prevents MIME type sniffing
- Referrer-Policy: Controls referrer information leakage
- Permissions-Policy: Disables browsing-topics API
"""
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
conn
|> put_resp_header("permissions-policy", "browsing-topics=()")
|> put_resp_header("content-security-policy", csp_header())
|> put_resp_header("x-frame-options", "DENY")
|> put_resp_header("x-content-type-options", "nosniff")
|> put_resp_header("referrer-policy", "strict-origin-when-cross-origin")
end
defp csp_header do
Enum.join(
[
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline' 'unsafe-hashes'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' ws: wss:",
"frame-ancestors 'none'"
],
"; "
)
end
end