Security Lines
LogStruct writes a security line when Rails blocks a request for a host it doesn't allow, when a request fails Rails' CSRF checks, and when Rails finds conflicting client IP headers (IP spoofing). Rails still handles each request as it would without LogStruct. CSRF and IP spoofing errors are also reported to your error tracker by default. Blocked hosts are logged only.
Blocked hosts
Rails' HostAuthorization middleware blocks requests whose Host header isn't in config.hosts, and it's only active where config.hosts isn't empty. Rails fills it in development; in test and production it's empty unless your app sets it. LogStruct gives the middleware a response app that writes a warn line, then responds:
- with your app's own
config.host_authorization[:response_app], if it set one (otherhost_authorizationsettings such asexcludeare kept); - otherwise with a 403 and a short "Blocked Host" HTML page, in every environment. (Without LogStruct, Rails responds with a 403 and an empty body, or with its own blocked-host page where it shows detailed exceptions, as in development.)
{
"src": "security",
"evt": "blocked_host",
"lvl": "warn",
"ts": "2026-10-17T03:21:07.025+00:00",
"path": "/health",
"method": "GET",
"source_ip": "203.0.113.10",
"user_agent": "Mozilla/5.0 (test)",
"referer": "https://app.example.com/posts/1",
"msg": "Blocked host detected: evil.example.com",
"blocked_host": "evil.example.com",
"blocked_hosts": [
"evil.example.com"
],
"prog": "Rails"
}This line has no req_id: HostAuthorization runs before the middleware that sets it. It isn't reported, whatever error_handling_modes.security_errors says.
CSRF errors
With protect_from_forgery with: :exception (Rails' default for ActionController::Base), Rails raises ActionController::InvalidAuthenticityToken for a request without a valid CSRF token, and ActionController::InvalidCrossOriginRequest when another site embeds a JavaScript response in a <script> tag. LogStruct registers an interceptor with Rails' DebugExceptions middleware, which is in the stack in every environment and sees the error before ShowExceptions turns it into a response. Both errors are an error line with the event csrf_violation and the error's message in msg:
{
"src": "security",
"evt": "csrf_violation",
"lvl": "error",
"ts": "2026-10-17T03:21:07.337+00:00",
"path": "/comments",
"method": "POST",
"source_ip": "203.0.113.10",
"user_agent": "Mozilla/5.0 (test)",
"referer": "https://app.example.com/posts/1",
"msg": "Can't verify CSRF token authenticity.",
"prog": "Rails",
"req_id": "08ab2f67-38ce-4348-9ea7-a615e856b069"
}Rails then responds as usual: 422 Unprocessable Content for both errors (in production, your public/422.html). Rails' own lines for the request are still written too, such as its "Can't verify CSRF token authenticity." warning.
IP spoofing
Rails raises ActionDispatch::RemoteIp::IpSpoofAttackError from request.remote_ip when the Client-IP and X-Forwarded-For headers disagree (with config.action_dispatch.ip_spoofing_check on, the default). It raises it only where something reads remote_ip: a request with those headers to an action that never reads it gets no line and its usual response. Where it's raised, the line is warn level:
{
"src": "security",
"evt": "ip_spoof",
"lvl": "warn",
"ts": "2026-10-17T03:21:07.379+00:00",
"path": "/whoami",
"method": "GET",
"source_ip": "203.0.113.10",
"user_agent": "Mozilla/5.0 (test)",
"referer": "https://app.example.com/posts/1",
"msg": "IP spoofing attack?! HTTP_CLIENT_IP=\"198.51.100.1\" HTTP_X_FORWARDED_FOR=\"192.0.2.7\"",
"client_ip": "198.51.100.1",
"x_forwarded_for": "192.0.2.7",
"prog": "Rails",
"req_id": "459628a4-7c21-4ffc-8471-6b23c2e2f408"
}Rails doesn't map this error to a status, so it responds with a 500 (in production, your public/500.html); in tests that don't render exceptions, the error is raised. An error raised in your controllers is logged by the DebugExceptions interceptor. One raised by a middleware outside ShowExceptions is logged by LogStruct's error middleware, which sits just before ActionDispatch::RemoteIp and raises the error again for your server to handle.
Fields
Every security line has:
| Field | Value |
|---|---|
| path | The request path, without the query string. |
| method | The HTTP method. |
| source_ip | The client's IP: Rails' remote_ip on CSRF lines, Rack's request.ip on blocked host and IP spoofing lines (and on CSRF lines when the request's IP headers are spoofed). |
| user_agent | The User-Agent header. |
| referer | The Referer header, without its query and fragment, which can carry reset or confirmation tokens. |
| msg | What happened (for CSRF and IP spoofing, the error's message). |
| req_id | The request ID, on CSRF and IP spoofing lines, as on every line logged during the request. |
And per event:
| Field | Value |
|---|---|
| blocked_host | blocked_host: the request's host. |
| blocked_hosts | blocked_host: every host Rails blocked: the Host header, and X-Forwarded-Host when the request has one. |
| x_forwarded_for | blocked_host and ip_spoof: the X-Forwarded-For header, when the request has one. |
| client_ip | ip_spoof: the Client-IP header. |
IP addresses are written as they are unless you turn on filters.ip_addresses (see Filtering Sensitive Data), which writes them as [IP], in msg too.
Reporting
The CSRF and IP spoofing lines are written in every mode. config.error_handling_modes.security_errors decides what happens next. A reported error is passed once to your error tracker (or error_reporting_handler) with the request's path, method, user agent and referer. "Local environments" are those in local_environments (development and test by default).
| security_errors | After the line |
|---|---|
| Ignore, Log | Nothing more. |
| Report (default) | The error is reported to your error tracker. |
| LogProduction | Production: nothing more. Local environments: as Raise. |
| ReportProduction | Production: the error is reported. Local environments: as Raise. |
| Raise | LogStruct raises the error inside Rails' DebugExceptions interceptor. Rails rescues it and responds as usual. The error isn't reported. |
Settings
config.integrations.enable_host_authorization(defaulttrue): blocked host lines.config.integrations.enable_rack_error_handler(defaulttrue): CSRF and IP spoofing lines, and the error lines for errors that get past Rails'ShowExceptions. With it on, when Rails can't parse a request's params (malformed JSON, a bad %-encoding), its debug line gives the body's size and type instead of the body, and the error line leaves out the input the parser's message quotes. Nothing could filter either.config.error_handling_modes.security_errors(defaultReport).
See Error Handling Configuration for the modes.
LogStruct.configure do |config|
# Blocked host lines (on by default)
config.integrations.enable_host_authorization = true
# CSRF and IP spoofing lines (on by default)
config.integrations.enable_rack_error_handler = true
# Log CSRF and IP spoofing errors and report them to your error tracker (the default).
# Log writes the line without reporting the error.
config.error_handling_modes.security_errors = LogStruct::ErrorHandlingMode::Report
endNot logged
- CSRF failures that Rails doesn't raise: with
protect_from_forgery with: :null_sessionor:reset_session, only Rails' own warning line is written. Rails' test environment turns forgery protection off, so tests have no CSRF lines unless they turn it on. - CSRF errors your controllers handle with their own
rescue_from: they never reachDebugExceptions. - Spoofed IP headers on requests where nothing reads
remote_ip.
rails error lines, written where Rails would log its own block of text, with the response's status. They aren't reported by LogStruct: your error tracker's Rails integration reports them, as it would without LogStruct. See Troubleshooting.