diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 624d7d4aa..36f2cd7d7 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -273,7 +273,7 @@ EvalState::EvalState(
 
             /* Apply access control if needed. */
             if (settings.restrictEval || settings.pureEval)
-                accessor = AllowListSourceAccessor::create(accessor, {},
+                accessor = AllowListSourceAccessor::create(accessor, {}, {},
                     [&settings](const CanonPath & path) -> RestrictedPathError {
                         auto modeInformation = settings.pureEval
                             ? "in pure evaluation mode (use '--impure' to override)"
diff --git a/src/libfetchers/filtering-source-accessor.cc b/src/libfetchers/filtering-source-accessor.cc
index b1ba84140..72a3fb4eb 100644
--- a/src/libfetchers/filtering-source-accessor.cc
+++ b/src/libfetchers/filtering-source-accessor.cc
@@ -58,18 +58,23 @@ void FilteringSourceAccessor::checkAccess(const CanonPath & path)
 struct AllowListSourceAccessorImpl : AllowListSourceAccessor
 {
     std::set<CanonPath> allowedPrefixes;
+    std::unordered_set<CanonPath> allowedPaths;
 
     AllowListSourceAccessorImpl(
         ref<SourceAccessor> next,
         std::set<CanonPath> && allowedPrefixes,
+        std::unordered_set<CanonPath> && allowedPaths,
         MakeNotAllowedError && makeNotAllowedError)
         : AllowListSourceAccessor(SourcePath(next), std::move(makeNotAllowedError))
         , allowedPrefixes(std::move(allowedPrefixes))
+        , allowedPaths(std::move(allowedPaths))
     { }
 
     bool isAllowed(const CanonPath & path) override
     {
-        return path.isAllowed(allowedPrefixes);
+        return
+            allowedPaths.contains(path)
+            || path.isAllowed(allowedPrefixes);
     }
 
     void allowPrefix(CanonPath prefix) override
@@ -81,9 +86,14 @@ struct AllowListSourceAccessorImpl : AllowListSourceAccessor
 ref<AllowListSourceAccessor> AllowListSourceAccessor::create(
     ref<SourceAccessor> next,
     std::set<CanonPath> && allowedPrefixes,
+    std::unordered_set<CanonPath> && allowedPaths,
     MakeNotAllowedError && makeNotAllowedError)
 {
-    return make_ref<AllowListSourceAccessorImpl>(next, std::move(allowedPrefixes), std::move(makeNotAllowedError));
+    return make_ref<AllowListSourceAccessorImpl>(
+        next,
+        std::move(allowedPrefixes),
+        std::move(allowedPaths),
+        std::move(makeNotAllowedError));
 }
 
 bool CachingFilteringSourceAccessor::isAllowed(const CanonPath & path)
diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc
index 2bef348de..58ba39889 100644
--- a/src/libfetchers/git-utils.cc
+++ b/src/libfetchers/git-utils.cc
@@ -1215,16 +1215,12 @@ ref<SourceAccessor> GitRepoImpl::getAccessor(
 ref<SourceAccessor> GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError)
 {
     auto self = ref<GitRepoImpl>(shared_from_this());
-    /* In case of an empty workdir, return an empty in-memory tree. We
-       cannot use AllowListSourceAccessor because it would return an
-       error for the root (and we can't add the root to the allow-list
-       since that would allow access to all its children). */
     ref<SourceAccessor> fileAccessor =
-        wd.files.empty()
-        ? makeEmptySourceAccessor()
-        : AllowListSourceAccessor::create(
+        AllowListSourceAccessor::create(
             makeFSSourceAccessor(path),
-            std::set<CanonPath> { wd.files },
+            std::set<CanonPath>{ wd.files },
+            // Always allow access to the root, but not its children.
+            std::unordered_set<CanonPath>{CanonPath::root},
             std::move(makeNotAllowedError)).cast<SourceAccessor>();
     if (exportIgnore)
         return make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt);
diff --git a/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh b/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh
index 0e6b71e9a..2b59f03ca 100644
--- a/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh
+++ b/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh
@@ -2,6 +2,8 @@
 
 #include "nix/util/source-path.hh"
 
+#include <unordered_set>
+
 namespace nix {
 
 /**
@@ -70,6 +72,7 @@ struct AllowListSourceAccessor : public FilteringSourceAccessor
     static ref<AllowListSourceAccessor> create(
         ref<SourceAccessor> next,
         std::set<CanonPath> && allowedPrefixes,
+        std::unordered_set<CanonPath> && allowedPaths,
         MakeNotAllowedError && makeNotAllowedError);
 
     using FilteringSourceAccessor::FilteringSourceAccessor;
diff --git a/tests/functional/flakes/meson.build b/tests/functional/flakes/meson.build
index 74ff3d91d..b8c650db4 100644
--- a/tests/functional/flakes/meson.build
+++ b/tests/functional/flakes/meson.build
@@ -29,7 +29,8 @@ suites += {
     'non-flake-inputs.sh',
     'relative-paths.sh',
     'symlink-paths.sh',
-    'debugger.sh'
+    'debugger.sh',
+    'source-paths.sh',
   ],
   'workdir': meson.current_source_dir(),
 }
diff --git a/tests/functional/flakes/source-paths.sh b/tests/functional/flakes/source-paths.sh
new file mode 100644
index 000000000..4709bf2fc
--- /dev/null
+++ b/tests/functional/flakes/source-paths.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+source ./common.sh
+
+requireGit
+
+repo=$TEST_ROOT/repo
+
+createGitRepo "$repo"
+
+cat > "$repo/flake.nix" <<EOF
+{
+  outputs = { ... }: {
+    x = 1;
+  };
+}
+EOF
+
+expectStderr 1 nix eval "$repo#x" | grepQuiet "error: Path 'flake.nix' in the repository \"$repo\" is not tracked by Git."
+
+git -C "$repo" add flake.nix
+
+[[ $(nix eval "$repo#x") = 1 ]]