Admin audit log does not capture IP addresses #9
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The
admin_audit_logtable has anip_address inetcolumn, but theAdminControllernever passesHttpServletRequestto the service layer. All audit log entries are saved withnullfor IP address, reducing forensic value.Affected files
AdminController.java— does not passHttpServletRequestto service methodsAdminService.java:66-68, 111-112, 130-131— saves audit logs withnullIPRecommended fix
HttpServletRequestparameter toAdminControllermethodsrequest.getRemoteAddr()(or theX-Forwarded-Forheader if behind a reverse proxy) toAdminServiceAdminAuditLogentitySeverity
Medium — reduces forensic and incident response capabilities.
👨💻 Kai — Frontend Engineer
Not much frontend surface to this one — the IP capture happens entirely in the backend. But a few things worth flagging from my side:
nullgracefully for older entries? I'd want to design the display component to tolerate both states from day one rather than assuming it's always present.X-Forwarded-Forheader (which can contain multiple IPs likeclient, proxy1, proxy2), the UI display component needs to handle that string form, not assume it's always a single clean address.Questions:
admin_audit_log? If so, should this issue link to it or wait for it?ipAddressas a nullable string or always guaranteed non-null post-fix?🔧 Backend Engineer — Spring Boot / PostgreSQL Specialist
Solid forensic improvement. The fix is straightforward but there are a few design decisions worth making deliberately:
On
request.getRemoteAddr()vs.X-Forwarded-For:getRemoteAddr()will return the reverse proxy's IP (e.g., nginx, Traefik) when deployed — not the actual client. We need to decide upfront: are we behind a proxy in production?X-Forwarded-Forheader extraction, but do it safely — Spring hasForwardedHeaderFilterwhich handles this properly and avoids header spoofing. Don't roll a manual header-parse.ForwardedHeaderFilteras a bean and rely onrequest.getRemoteAddr()after the filter does its job, rather than readingX-Forwarded-Formanually in the controller.On passing
HttpServletRequestthrough the service layer:HttpServletRequestall the way down toAdminService. That couples the service layer to the HTTP context, which makes it harder to test and violates layer separation.String ipAddressparameter. The service stays portable and testable.On the
inetcolumn type:inettype validates IP format. If the value coming in is malformed (e.g., a spoofed multi-valueX-Forwarded-For), the insert will throw. Make sure we sanitize/truncate to just the first valid IP before storing, or catch the constraint violation gracefully.Questions:
AdminAuditLoga JPA entity mapped toinetas aString? If so, does Hibernate handle theinet→varcharcast cleanly, or do we need a custom type converter?🧪 QA Engineer
Good find — null IP addresses in the audit log are a silent data quality problem that's easy to miss until you need the data in an incident. Here's the test coverage I'd want to see:
Unit tests (AdminService):
shouldSaveAuditLogWithIpAddressWhenDirectlyConnected()— IP extracted from request, stored correctlyshouldSaveAuditLogWithClientIpWhenBehindReverseProxy()—X-Forwarded-Forvalue stored (first IP only)shouldSaveAuditLogWithNullIpWhenRequestIsNull()— defensive: service doesn't blow up if IP is unavailableIntegration tests (AdminController + DB):
admin_audit_logrow has a non-nullip_addressX-Forwarded-Forheader and verify the stored value matches the client IP, not the proxy IPX-Forwarded-For(e.g.,"not-an-ip, 1.2.3.4") — assert it doesn't throw a 500; either sanitize gracefully or store nullRegression check:
AdminServiceunit tests still pass after the signature change — ifHttpServletRequestis extracted in the controller and passed as aString, the service tests should become simpler, not more complexEdge cases I'm thinking about:
inetcolumn and the Hibernate mapping handle them correctly?Questions:
AdminServicemethods at lines 66–68, 111–112, 130–131 are affected? Knowing the action types helps me enumerate test scenarios.🔐 Sable — Security Engineer
This is a legitimate forensic gap — admin actions without a source IP are nearly useless in an incident response scenario. But the fix itself introduces a security surface I want to flag explicitly:
X-Forwarded-Forheader spoofing:X-Forwarded-For: 1.2.3.4to any value and log a fake IP.X-Forwarded-Forwhen the actual network source (getRemoteAddr()) is a known, trusted proxy.ForwardedHeaderFilterwithRelativeRedirectFilterconfiguration handles this correctly — do not hand-roll the header extraction.IP logging and data minimization:
admin_audit_logrows?Audit log integrity:
admin_audit_logmust remain append-only. The fix should not introduce any UPDATE path to existing rows. Confirm the repository layer has no UPDATE/DELETE methods for this entity.Sensitive data in logs:
log.error), confirm we're not inadvertently logging the full request including session cookies or request bodies.Questions:
ForwardedHeaderFilterbean registered, or will this be new infrastructure?🎨 Atlas — UI/UX Designer
This is a backend data capture fix with no immediate UI impact, but it's worth thinking about how the audit log data will eventually be presented — because that shapes what we need from the API.
Future admin audit log view (whenever it's built):
DM Mono(our monospace token font) to make them scannable — consistent with how we'd treat timestamps, UUIDs, and other structured technical strings.nullIP will need a graceful fallback display: a dash (—) or an "Unknown" label in--color-subtlerather than an empty cell, which looks broken.Data display considerations:
192.168.1.1) and IPv6 (2001:db8::1) have very different display widths. The column in the admin table will need enough space for IPv6 without wrapping — or we use a truncate + tooltip pattern for long values.No design action needed for this issue — but please ensure the API response DTO includes
ipAddressas a nullable string field so the frontend component can handle it without a breaking schema change later.Questions: