← Back to task

Commit b3d71191

commit b3d711918feb60f2179fc9bfcad36616102aebed
Author: Ben Sima <ben@bensima.com>
Date:   Thu Jan 1 14:11:01 2026

    Add ReadWritePaths to Hardening schema (t-317)
    
    Added hardeningReadWritePaths :: [Text] field to Hardening type.
    Updated Systemd.hs to combine DATA_DIR and readWritePaths into
    a single ReadWritePaths directive.
    
    This fixes Ava not being able to write to /var/lib/omni/tasks.db
    when ProtectSystem=strict is enabled.
    
    Task-Id: t-317

diff --git a/Omni/Deploy/Manifest.hs b/Omni/Deploy/Manifest.hs
index 532ec4c0..ea99d2c1 100644
--- a/Omni/Deploy/Manifest.hs
+++ b/Omni/Deploy/Manifest.hs
@@ -180,7 +180,8 @@ data Hardening = Hardening
   { hardeningDynamicUser :: Bool,
     hardeningPrivateTmp :: Bool,
     hardeningProtectSystem :: Text,
-    hardeningProtectHome :: Bool
+    hardeningProtectHome :: Bool,
+    hardeningReadWritePaths :: [Text]
   }
   deriving (Show, Eq, Generic)
 
@@ -198,6 +199,9 @@ instance Aeson.FromJSON Hardening where
         <*> o
         .:? "protectHome"
         .!= True
+        <*> o
+        .:? "readWritePaths"
+        .!= []
 
 instance Aeson.ToJSON Hardening where
   toJSON Hardening {..} =
@@ -205,11 +209,12 @@ instance Aeson.ToJSON Hardening where
       [ "dynamicUser" .= hardeningDynamicUser,
         "privateTmp" .= hardeningPrivateTmp,
         "protectSystem" .= hardeningProtectSystem,
-        "protectHome" .= hardeningProtectHome
+        "protectHome" .= hardeningProtectHome,
+        "readWritePaths" .= hardeningReadWritePaths
       ]
 
 defaultHardening :: Hardening
-defaultHardening = Hardening False True "strict" True
+defaultHardening = Hardening False True "strict" True []
 
 data Service = Service
   { serviceName :: Text,
diff --git a/Omni/Deploy/Systemd.hs b/Omni/Deploy/Systemd.hs
index 99d48200..8d1f28b3 100644
--- a/Omni/Deploy/Systemd.hs
+++ b/Omni/Deploy/Systemd.hs
@@ -106,9 +106,14 @@ generateUnit Service {..} =
         ++ readWritePathsLine
 
     readWritePathsLine =
-      case Map.lookup "DATA_DIR" serviceEnv of
-        Just dataDir -> ["ReadWritePaths=" <> dataDir]
-        Nothing -> []
+      let dataDirPaths = case Map.lookup "DATA_DIR" serviceEnv of
+            Just dataDir -> [dataDir]
+            Nothing -> []
+          hardeningPaths = hardeningReadWritePaths serviceHardening
+          allPaths = dataDirPaths ++ hardeningPaths
+      in if null allPaths
+         then []
+         else ["ReadWritePaths=" <> Text.intercalate " " allPaths]
 
     installSection =
       [ "",
@@ -222,7 +227,7 @@ mkTestService name path =
       serviceEnvFile = Nothing,
       serviceHttp = Nothing,
       serviceSystemd = Systemd ["network-online.target"] [] "on-failure" 5,
-      serviceHardening = Hardening False True "strict" True,
+      serviceHardening = Hardening False True "strict" True [],
       serviceRevision = Nothing
     }
 
@@ -292,7 +297,7 @@ test_generateUnitWithHardening =
   Test.unit "generates unit with hardening" <| do
     let svc =
           (mkTestService "hardened" "/nix/store/abc")
-            { serviceHardening = Hardening False True "full" True
+            { serviceHardening = Hardening False True "full" True []
             }
         unit = generateUnit svc
     Text.isInfixOf "PrivateTmp=yes" unit Test.@=? True