using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace ErsatzTV.Filters; /// /// Restricts an endpoint to loopback callers (127.0.0.0/8, ::1). Used for the in-process scanner /// callback surface (/api/v1/scan/*), which is always reached over /// http://localhost:{UiPort} from the co-located scanner child process. Replaces relying /// on a guessable scan-id GUID as the sole gate (issue #285). This is only spoof-resistant when /// ForwardedHeaders trust is restricted (KnownProxies/KnownNetworks configured), since the /// forwarded-headers middleware rewrites . /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class LocalhostOnlyAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { IPAddress remoteIp = context.HttpContext.Connection.RemoteIpAddress; if (remoteIp is null || !IsLoopback(remoteIp)) { context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden); } } private static bool IsLoopback(IPAddress address) { if (IPAddress.IsLoopback(address)) { return true; } // A loopback IPv4 address can arrive mapped into IPv6 (::ffff:127.0.0.1). return address.IsIPv4MappedToIPv6 && IPAddress.IsLoopback(address.MapToIPv4()); } }