diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# effectful-config-file-secretspec
+
+SecretSpec-backed Secrets effect handler for effectful-config-file.
+
+## Installing libsecretspec
+
+The `secretspec` Haskell package links against the libsecretspec C library,
+found through `pkg-config`. Build it from the
+[secretspec](https://github.com/cachix/secretspec) tag matching the
+`secretspec` extra-dep in `stack.yaml`.
+
+Requirements: [rustup](https://rustup.rs), `pkg-config` and
+[cargo-c](https://github.com/lu-zero/cargo-c) (`cargo install cargo-c`).
+
+```bash
+git clone --depth 1 --branch v0.21.1 https://github.com/cachix/secretspec.git
+cd secretspec
+cargo cinstall -p libsecretspec --manifest-path Cargo.toml \
+  --library-type staticlib \
+  --prefix "$HOME/.libsecretspec" \
+  --bindir lib \
+  --libdir lib \
+  --pkgconfigdir lib/pkgconfig
+export PKG_CONFIG_PATH="$HOME/.libsecretspec/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
+```
+
+Check it with `pkg-config --libs libsecretspec`, then build with `stack build`.
diff --git a/effectful-config-file-secretspec.cabal b/effectful-config-file-secretspec.cabal
new file mode 100644
--- /dev/null
+++ b/effectful-config-file-secretspec.cabal
@@ -0,0 +1,66 @@
+cabal-version: 2.2
+
+-- This file has been generated from package.yaml by hpack version 0.39.6.
+--
+-- see: https://github.com/sol/hpack
+
+name:           effectful-config-file-secretspec
+version:        0.1.0.0
+synopsis:       SecretSpec backend for effectful-config-file
+description:    SecretSpec-backed Secrets effect handler for effectful-config-file
+category:       Configuration
+author:         drlkf
+maintainer:     drlkf@drlkf.net
+copyright:      2026 drlkf
+license:        BSD-3-Clause
+build-type:     Simple
+extra-source-files:
+    README.md
+
+library
+  exposed-modules:
+      Effectful.Config.Secrets.SecretSpec
+  other-modules:
+      Paths_effectful_config_file_secretspec
+  autogen-modules:
+      Paths_effectful_config_file_secretspec
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
+  build-tool-depends:
+      hspec-discover:hspec-discover
+  build-depends:
+      base >=4.7 && <5
+    , containers >=0.2 && <1
+    , effectful >=2.6 && <3
+    , effectful-config-file >=0.1 && <2
+    , secretspec >=0.21 && <1
+    , text >=2.1 && <3
+  default-language: Haskell2010
+
+test-suite effectful-config-file-secretspec-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Effectful.Config.Secrets.SecretSpecSpec
+      Paths_effectful_config_file_secretspec
+  autogen-modules:
+      Paths_effectful_config_file_secretspec
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
+  build-tool-depends:
+      hspec-discover:hspec-discover
+  build-depends:
+      base >=4.7 && <5
+    , containers >=0.2 && <1
+    , effectful >=2.6 && <3
+    , effectful-config-file >=0.1 && <2
+    , effectful-config-file-secretspec
+    , filepath
+    , hspec
+    , lens
+    , secretspec >=0.21 && <1
+    , temporary
+    , text >=2.1 && <3
+  default-language: Haskell2010
diff --git a/src/Effectful/Config/Secrets/SecretSpec.hs b/src/Effectful/Config/Secrets/SecretSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/Effectful/Config/Secrets/SecretSpec.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE TypeOperators #-}
+
+module Effectful.Config.Secrets.SecretSpec (
+  runSecretSpec,
+) where
+
+import qualified Data.Map.Strict as Map
+import Effectful (Eff, IOE, liftIO, type (:>))
+import Effectful.Config.Secrets (Secrets (..), SecretsError (..))
+import Effectful.Dispatch.Dynamic (interpret)
+import Effectful.Error.Static (Error, throwError)
+import Effectful.Exception (bracket)
+import SecretSpec (Builder, builder, close, get, load, resolvedSecrets)
+
+-- | @since 0.1.0.0
+runSecretSpec
+  :: IOE :> es
+  => Error SecretsError :> es
+  => (Builder -> Builder)
+  -> Eff (Secrets : es) a
+  -> Eff es a
+runSecretSpec f act =
+  bracket (liftIO (load (f builder))) (liftIO . close) $ \r ->
+    interpret
+      ( \_ -> \case
+          GetSecret k ->
+            maybe
+              (throwError (SecretNotFound k))
+              pure
+              (Map.lookup k (resolvedSecrets r) >>= get)
+      )
+      act
diff --git a/test/Effectful/Config/Secrets/SecretSpecSpec.hs b/test/Effectful/Config/Secrets/SecretSpecSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Effectful/Config/Secrets/SecretSpecSpec.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Effectful.Config.Secrets.SecretSpecSpec (
+  spec,
+) where
+
+import Control.Concurrent.MVar (newMVar)
+import Control.Lens (makeLenses)
+import Data.Config (ConfigValue (..))
+import qualified Data.Text as T (pack)
+import Effectful (runEff)
+import Effectful.Config (getConfigValue)
+import Effectful.Config.Log (runLog)
+import Effectful.Config.Secrets (SecretsError (..), getSecret)
+import Effectful.Config.Secrets.SecretSpec (runSecretSpec)
+import Effectful.Error.Static (runErrorNoCallStack)
+import Effectful.Reader.Static (runReader)
+import SecretSpec (Builder, withPath, withProvider, withReason)
+import System.FilePath ((</>))
+import System.IO.Temp (withSystemTempDirectory)
+import Test.Hspec (Spec, describe, it, shouldBe)
+
+newtype SecretConfig = SecretConfig
+  { _secret_val :: ConfigValue String
+  }
+  deriving (Eq, Show)
+
+makeLenses ''SecretConfig
+
+silentLog :: a -> b -> c -> d -> IO ()
+silentLog _ _ _ _ = pure ()
+
+withManifest :: (FilePath -> (Builder -> Builder) -> IO a) -> IO a
+withManifest act = withSystemTempDirectory "secretspec" $ \dir -> do
+  writeFile (dir </> ".env") "MY_SECRET=hunter2\n"
+  writeFile
+    (dir </> "secretspec.toml")
+    ( unlines
+        [ "[project]"
+        , "name = \"test\""
+        , "revision = \"1.0\""
+        , ""
+        , "[profiles.default]"
+        , "MY_SECRET = { description = \"test\", required = true }"
+        ]
+    )
+  act
+    dir
+    ( withPath (T.pack (dir </> "secretspec.toml"))
+        . withProvider (T.pack ("dotenv://" <> (dir </> ".env")))
+        . withReason "test"
+    )
+
+spec :: Spec
+spec = do
+  describe "runSecretSpec" $ do
+    it "resolves a declared secret" $ withManifest $ \_ cfg -> do
+      result <-
+        runEff
+          . runErrorNoCallStack @SecretsError
+          . runSecretSpec cfg
+          $ getSecret "MY_SECRET"
+
+      result `shouldBe` Right "hunter2"
+
+    it "errors on an undeclared secret" $ withManifest $ \_ cfg -> do
+      result <-
+        runEff
+          . runErrorNoCallStack @SecretsError
+          . runSecretSpec cfg
+          $ getSecret "NOPE"
+
+      result `shouldBe` Left (SecretNotFound "NOPE")
+
+  describe "getConfigValue" $ do
+    it "resolves a Secret config value via secretspec" $ withManifest $ \_ cfg -> do
+      p <- newMVar (SecretConfig (Secret "MY_SECRET"))
+      result <-
+        runEff
+          . runErrorNoCallStack @SecretsError
+          . runSecretSpec cfg
+          . runLog silentLog
+          . runReader p
+          $ getConfigValue secret_val
+
+      result `shouldBe` Right "hunter2"
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+{-# OPTIONS_GHC -Wno-missing-export-lists #-}
