diff --git a/src/libexpr/primops/fetchGit.cc b/src/libexpr/primops/fetchGit.cc
index 62e9dfc0e..bbf13c87b 100644
--- a/src/libexpr/primops/fetchGit.cc
+++ b/src/libexpr/primops/fetchGit.cc
@@ -22,9 +22,6 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
     std::optional<std::string> ref, std::string rev,
     const std::string & name)
 {
-    if (evalSettings.pureEval && rev == "")
-        throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
-
     if (!ref && rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.git")) {
 
         bool clean = true;
@@ -218,6 +215,9 @@ static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Va
     // whitelist. Ah well.
     state.checkURI(url);
 
+    if (evalSettings.pureEval && rev == "")
+        throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
+
     auto gitInfo = exportGit(state.store, url, ref, rev, name);
 
     state.mkAttrs(v, 8);
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index 66f49f374..cfe1bd871 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -27,9 +27,6 @@ std::regex commitHashRegex("^[0-9a-fA-F]{40}$");
 HgInfo exportMercurial(ref<Store> store, const std::string & uri,
     std::string rev, const std::string & name)
 {
-    if (evalSettings.pureEval && rev == "")
-        throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
-
     if (rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.hg")) {
 
         bool clean = runProgram("hg", true, { "status", "-R", uri, "--modified", "--added", "--removed" }) == "";
@@ -203,6 +200,9 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
     // whitelist. Ah well.
     state.checkURI(url);
 
+    if (evalSettings.pureEval && rev == "")
+        throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
+
     auto hgInfo = exportMercurial(state.store, url, rev, name);
 
     state.mkAttrs(v, 8);
diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc
index 4d027558d..1e70ccbd6 100644
--- a/src/libexpr/primops/flake.cc
+++ b/src/libexpr/primops/flake.cc
@@ -162,16 +162,17 @@ static Flake getFlake(EvalState & state, const FlakeRef & flakeRef)
     return flake;
 }
 
-/* Given a set of flake references, recursively fetch them and their
+/* Given a flake reference, recursively fetch it and its
    dependencies. */
-static std::map<FlakeId, Flake> resolveFlakes(EvalState & state, const std::vector<FlakeRef> & flakeRefs)
+static std::map<FlakeId, Flake> resolveFlake(EvalState & state,
+    const FlakeRef & topRef, bool impureTopRef)
 {
     std::map<FlakeId, Flake> done;
-    std::queue<FlakeRef> todo;
-    for (auto & i : flakeRefs) todo.push(i);
+    std::queue<std::tuple<FlakeRef, bool>> todo;
+    todo.push({topRef, impureTopRef});
 
     while (!todo.empty()) {
-        auto flakeRef = todo.front();
+        auto [flakeRef, impureRef] = todo.front();
         todo.pop();
 
         if (auto refData = std::get_if<FlakeRef::IsFlakeId>(&flakeRef.data)) {
@@ -179,12 +180,15 @@ static std::map<FlakeId, Flake> resolveFlakes(EvalState & state, const std::vect
             flakeRef = lookupFlake(state, flakeRef);
         }
 
+        if (evalSettings.pureEval && !flakeRef.isImmutable() && !impureRef)
+            throw Error("mutable flake '%s' is not allowed in pure mode; use --no-pure-eval to disable", flakeRef.to_string());
+
         auto flake = getFlake(state, flakeRef);
 
         if (done.count(flake.id)) continue;
 
         for (auto & require : flake.requires)
-            todo.push(require);
+            todo.push({require, false});
 
         done.emplace(flake.id, flake);
     }
@@ -194,9 +198,19 @@ static std::map<FlakeId, Flake> resolveFlakes(EvalState & state, const std::vect
 
 static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
-    auto flakeUri = FlakeRef(state.forceStringNoCtx(*args[0], pos));
+    auto flakeUri = state.forceStringNoCtx(*args[0], pos);
 
-    auto flakes = resolveFlakes(state, {flakeUri});
+    // FIXME: temporary hack to make the default installation source
+    // work.
+    bool impure = false;
+    if (hasPrefix(flakeUri, "impure:")) {
+        flakeUri = std::string(flakeUri, 7);
+        impure = true;
+    }
+
+    auto flakeRef = FlakeRef(flakeUri);
+
+    auto flakes = resolveFlake(state, flakeUri, impure);
 
     auto vResult = state.allocValue();
 
diff --git a/src/libexpr/primops/flakeref.cc b/src/libexpr/primops/flakeref.cc
index 447b56822..639313f21 100644
--- a/src/libexpr/primops/flakeref.cc
+++ b/src/libexpr/primops/flakeref.cc
@@ -136,4 +136,18 @@ std::string FlakeRef::to_string() const
     else abort();
 }
 
+bool FlakeRef::isImmutable() const
+{
+    if (auto refData = std::get_if<FlakeRef::IsFlakeId>(&data))
+        return (bool) refData->rev;
+
+    else if (auto refData = std::get_if<FlakeRef::IsGitHub>(&data))
+        return (bool) refData->rev;
+
+    else if (auto refData = std::get_if<FlakeRef::IsGit>(&data))
+        return (bool) refData->rev;
+
+    else abort();
+}
+
 }
diff --git a/src/libexpr/primops/flakeref.hh b/src/libexpr/primops/flakeref.hh
index 8559317e0..ad0cf8630 100644
--- a/src/libexpr/primops/flakeref.hh
+++ b/src/libexpr/primops/flakeref.hh
@@ -149,10 +149,7 @@ struct FlakeRef
 
     /* Check whether this is an "immutable" flake reference, that is,
        one that contains a commit hash or content hash. */
-    bool isImmutable() const
-    {
-        abort(); // TODO
-    }
+    bool isImmutable() const;
 };
 
 }
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index faad057a7..b4584f168 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -31,9 +31,11 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
     if (file != "")
         state.evalFile(lookupFileArg(state, file), *vSourceExpr);
     else {
+        // FIXME: remove "impure" hack, call some non-user-accessible
+        // variant of getFlake instead.
         auto fun = state.parseExprFromString(
              "builtins.mapAttrs (flakeName: flakeInfo:"
-             "  (getFlake flakeInfo.uri).${flakeName}.provides.packages or {})", "/");
+             "  (getFlake (\"impure:\" + flakeInfo.uri)).${flakeName}.provides.packages or {})", "/");
         auto vFun = state.allocValue();
         state.eval(fun, *vFun);
         auto vRegistry = state.makeFlakeRegistryValue();