From 70cd4f1ec55c6ea524559190e0c61993d921f979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Bon=C3=A9?= Date: Fri, 3 Oct 2025 17:28:01 +0200 Subject: [PATCH] Do not serve derivations with /nix-support/private --- nix-serve-ng.cabal | 1 + src/Main.hs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/nix-serve-ng.cabal b/nix-serve-ng.cabal index d824e4d..b8d8c41 100644 --- a/nix-serve-ng.cabal +++ b/nix-serve-ng.cabal @@ -48,6 +48,7 @@ executable nix-serve , base32 , bytestring , charset + , directory , http-types , managed , megaparsec diff --git a/src/Main.hs b/src/Main.hs index f7f354d..ce3b75f 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -10,6 +10,7 @@ import Control.Monad.IO.Class (liftIO) import Data.ByteString (ByteString) import Data.CharSet.ByteSet (ByteSet(..)) import Data.Function ((&)) +import Data.Word (Word8) import Network.Socket (SockAddr(..)) import Network.Wai (Application) import Nix (NoSuchPath(..), PathInfo(..)) @@ -35,6 +36,7 @@ import qualified Network.Wai.Middleware.RequestLogger as RequestLogger import qualified Nix import qualified Options import qualified Options.Applicative as Options +import qualified System.Directory as Directory import qualified System.Environment as Environment data ApplicationOptions = ApplicationOptions @@ -54,6 +56,20 @@ validHashPartBytes = <> [ 0x76 .. 0x7A ] -- vwxyz ) +type HostAddressTuple = (Word8, Word8, Word8, Word8) + +isInWhitelist :: Socket.HostAddress -> Bool +isInWhitelist host = any (uncurry (inRange $ Socket.hostAddressToTuple host)) allowedIPs + where + allowedIPs :: [(HostAddressTuple, HostAddressTuple)] + allowedIPs = [ + ((127,0,0,1), (127,0,0,1)), + ((10,0,0,1), (10,255,255,254)), + ((192,168,72,1), (192,168,79,254)) + ] + + inRange ip a b = ip >= a && ip <= b + validHashPart :: ByteString -> Bool validHashPart hash = ByteString.all (`ByteSet.member` validHashPartBytes) hash @@ -250,6 +266,27 @@ makeApplication ApplicationOptions{..} request respond = do done response + let privateFilePath = ByteString.Char8.unpack storePath ++ "/nix-support/private" + isPrivate <- liftIO $ Directory.doesPathExist privateFilePath + + let isLocal = case Wai.remoteHost request of + SockAddrInet _ host -> isInWhitelist host + _ -> False + + Monad.when (isPrivate && not isLocal) do + let headers = [ ("Content-Type", "text/plain") ] + + let builder = "Forbidden.\n" + + let response = + Wai.responseBuilder + Types.status403 + headers + builder + + done response + + let streamingBody write flush = do result <- Nix.dumpPath hashPart callback