diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## 0.3.3.0 - 2022-05-21
+
+### Added
+
+ - Nix 2.9 support
+
+
 ## 0.3.2.0 - 2022-05-17
 
 ### Added
diff --git a/hercules-ci-cnix-expr.cabal b/hercules-ci-cnix-expr.cabal
--- a/hercules-ci-cnix-expr.cabal
+++ b/hercules-ci-cnix-expr.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.4
 
 name:           hercules-ci-cnix-expr
-version:        0.3.2.0
+version:        0.3.3.0
 synopsis:       Bindings for the Nix evaluator
 category:       Nix, CI, Testing, DevOps
 homepage:       https://docs.hercules-ci.com
diff --git a/src/Hercules/CNix/Expr.hs b/src/Hercules/CNix/Expr.hs
--- a/src/Hercules/CNix/Expr.hs
+++ b/src/Hercules/CNix/Expr.hs
@@ -305,7 +305,11 @@
             // case to return true when it is not a bool. That logic was added
             // because an empty attrset was found here, observed in
             // nixpkgs master 67e2de195a4aa0a50ffb1e1ba0b4fb531dca67dc
+#if NIX_IS_AT_LEAST(2,9,0)
+            return evalState.forceBool(*iter->value, iter->pos);
+#else
             return evalState.forceBool(*iter->value, *iter->pos);
+#endif
           }
         } |]
 
@@ -329,14 +333,22 @@
 mkNullableRawValue p | p == nullPtr = pure Nothing
 mkNullableRawValue p = Just <$> mkRawValue p
 
-getAttrs :: Value NixAttrs -> IO (Map ByteString RawValue)
-getAttrs (Value (RawValue v)) = do
+getAttrs :: Ptr EvalState -> Value NixAttrs -> IO (Map ByteString RawValue)
+getAttrs evalState (Value (RawValue v)) = do
   begin <- [C.exp| Attr *{ $(Value *v)->attrs->begin() }|]
   end <- [C.exp| Attr *{ $(Value *v)->attrs->end() }|]
   let gather :: Map ByteString RawValue -> Ptr Attr' -> IO (Map ByteString RawValue)
       gather acc i | i == end = pure acc
       gather acc i = do
+#if NIX_IS_AT_LEAST(2,9,0)
+        name <- unsafeMallocBS [C.block| const char *{
+          EvalState &evalState = *$(EvalState *evalState);
+          SymbolStr str = evalState.symbols[$(Attr *i)->name];
+          return strdup(static_cast<std::string>(str).c_str());
+        }|]
+#else
         name <- unsafeMallocBS [C.exp| const char *{ strdup(static_cast<std::string>($(Attr *i)->name).c_str()) } |]
+#endif
         value <- mkRawValue =<< [C.exp| Value *{ new (NoGC) Value(*$(Attr *i)->value) } |]
         let acc' = M.insert name value acc
         seq acc' pass
diff --git a/src/Hercules/CNix/Expr/Schema.hs b/src/Hercules/CNix/Expr/Schema.hs
--- a/src/Hercules/CNix/Expr/Schema.hs
+++ b/src/Hercules/CNix/Expr/Schema.hs
@@ -318,7 +318,8 @@
 
 dictionaryToMap :: MonadEval m => PSObject (Dictionary w) -> m (Map ByteString (PSObject w))
 dictionaryToMap dict = do
-  (liftIO . Expr.getAttrs =<< check dict)
+  es <- ask
+  (liftIO . Expr.getAttrs es =<< check dict)
     <&> M.mapWithKey
       ( \name b ->
           PSObject {value = b, provenance = Attribute (provenance dict) (decodeUtf8With lenientDecode name)}
