diff --git a/e2e/test/scenarios/permissions/sandboxes.cy.spec.js b/e2e/test/scenarios/permissions/sandboxes.cy.spec.js
index 20ee10d75622b4a89547546ae74ad53a4b5d4d43..d38103eb3663365b572096b11efebf4b93e1d873 100644
--- a/e2e/test/scenarios/permissions/sandboxes.cy.spec.js
+++ b/e2e/test/scenarios/permissions/sandboxes.cy.spec.js
@@ -585,122 +585,92 @@ describeEE("formatting > sandboxes", () => {
         cy.findAllByText("McClure-Lockman");
       });
 
-      /**
-       * This issue (metabase-enterprise#520) has a peculiar quirk:
-       *  - It works ONLY if SQL question is first run (`result_metadata` builds), and then the question is saved.
-       *  - In a real-world scenario it is quite possible for an admin to save that SQL question without running it first. This fails!
-       *  (more info: https://github.com/metabase/metabase-enterprise/issues/520#issuecomment-772528159)
-       *
-       * That's why this test has 2 versions that reflect both scenarios. We'll call them "normal" and "workaround".
-       * Until the underlying issue is fixed, "normal" scenario will be skipped.
-       *
-       * Related issues: metabase#10474, metabase#14629
-       *
-       * Update 2024/11/07:
-       * It's expected that this test fails - the issue is still there.
-       * See https://github.com/metabase/metabase/issues/49671 for a proposed fix.
-       */
-      ["normal", "workaround"].forEach(test => {
-        it(
-          `${test.toUpperCase()} version:\n advanced sandboxing should not ignore data model features like object detail of FK (metabase-enterprise#520)`,
-          { tags: "@quarantine" },
-          () => {
-            cy.intercept("POST", "/api/card/*/query").as("cardQuery");
-            cy.intercept("PUT", "/api/card/*").as("questionUpdate");
-
-            cy.createNativeQuestion({
-              name: "EE_520_Q1",
-              native: {
-                query:
-                  "SELECT * FROM ORDERS WHERE USER_ID={{sandbox}} AND TOTAL > 10",
-                "template-tags": {
-                  sandbox: {
-                    "display-name": "Sandbox",
-                    id: "1115dc4f-6b9d-812e-7f72-b87ab885c88a",
-                    name: "sandbox",
-                    type: "number",
-                  },
-                },
+      it("Advanced sandboxing should not ignore data model features like object detail of FK (metabase-enterprise#520)", () => {
+        cy.intercept("POST", "/api/card/*/query").as("cardQuery");
+        cy.intercept("PUT", "/api/card/*").as("questionUpdate");
+
+        cy.createNativeQuestion({
+          name: "EE_520_Q1",
+          native: {
+            query:
+              "SELECT * FROM ORDERS WHERE USER_ID={{sandbox}} AND TOTAL > 10",
+            "template-tags": {
+              sandbox: {
+                "display-name": "Sandbox",
+                id: "1115dc4f-6b9d-812e-7f72-b87ab885c88a",
+                name: "sandbox",
+                type: "number",
               },
-            }).then(({ body: { id: CARD_ID } }) => {
-              test === "workaround"
-                ? runQuestion({ question: CARD_ID, sandboxValue: "1" })
-                : null;
-
-              cy.sandboxTable({
-                table_id: ORDERS_ID,
-                card_id: CARD_ID,
-                attribute_remappings: {
-                  attr_uid: ["variable", ["template-tag", "sandbox"]],
-                },
-              });
-            });
-
-            cy.createNativeQuestion({
-              name: "EE_520_Q2",
-              native: {
-                query:
-                  "SELECT * FROM PRODUCTS WHERE CATEGORY={{sandbox}} AND PRICE > 10",
-                "template-tags": {
-                  sandbox: {
-                    "display-name": "Sandbox",
-                    id: "3d69ba99-7076-2252-30bd-0bb8810ba895",
-                    name: "sandbox",
-                    type: "text",
-                  },
-                },
+            },
+          },
+        }).then(({ body: { id: CARD_ID } }) => {
+          runQuestion({ question: CARD_ID, sandboxValue: "1" });
+
+          cy.sandboxTable({
+            table_id: ORDERS_ID,
+            card_id: CARD_ID,
+            attribute_remappings: {
+              attr_uid: ["variable", ["template-tag", "sandbox"]],
+            },
+          });
+        });
+
+        cy.createNativeQuestion({
+          name: "EE_520_Q2",
+          native: {
+            query:
+              "SELECT * FROM PRODUCTS WHERE CATEGORY={{sandbox}} AND PRICE > 10",
+            "template-tags": {
+              sandbox: {
+                "display-name": "Sandbox",
+                id: "3d69ba99-7076-2252-30bd-0bb8810ba895",
+                name: "sandbox",
+                type: "text",
               },
-            }).then(({ body: { id: CARD_ID } }) => {
-              test === "workaround"
-                ? runQuestion({
-                    question: CARD_ID,
-                    sandboxValue: "Widget",
-                  })
-                : null;
-
-              cy.sandboxTable({
-                table_id: PRODUCTS_ID,
-                card_id: CARD_ID,
-                attribute_remappings: {
-                  attr_cat: ["variable", ["template-tag", "sandbox"]],
-                },
-              });
-            });
-
-            cy.signOut();
-            cy.signInAsSandboxedUser();
-
-            openOrdersTable();
-
-            cy.log("Reported failing on v1.36.x");
-
-            cy.log(
-              "It should show remapped Display Values instead of Product ID",
-            );
-            cy.get("[data-testid=cell-data]")
-              .contains("Awesome Concrete Shoes")
-              .click();
-            // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-            cy.findByText(/View details/i).click();
-
-            cy.log(
-              "It should show object details instead of filtering by this Product ID",
-            );
-            // The name of this Vendor is visible in "details" only
-            cy.findByTestId("object-detail");
-            cy.findAllByText("McClure-Lockman");
-
-            /**
-             * Helper function related to this test only!
-             */
-            function runQuestion({ question, sandboxValue } = {}) {
-              // Run the question
-              cy.visit(`/question/${question}?sandbox=${sandboxValue}`);
-              // Wait for results
-              cy.wait("@cardQuery");
-            }
+            },
           },
+        }).then(({ body: { id: CARD_ID } }) => {
+          runQuestion({ question: CARD_ID, sandboxValue: "Widget" });
+
+          cy.sandboxTable({
+            table_id: PRODUCTS_ID,
+            card_id: CARD_ID,
+            attribute_remappings: {
+              attr_cat: ["variable", ["template-tag", "sandbox"]],
+            },
+          });
+        });
+
+        cy.signOut();
+        cy.signInAsSandboxedUser();
+
+        openOrdersTable();
+
+        cy.log("Reported failing on v1.36.x");
+
+        cy.log("It should show remapped Display Values instead of Product ID");
+        cy.get("[data-testid=cell-data]")
+          .contains("Awesome Concrete Shoes")
+          .click();
+        // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
+        cy.findByText(/View details/i).click();
+
+        cy.log(
+          "It should show object details instead of filtering by this Product ID",
         );
+        // The name of this Vendor is visible in "details" only
+        cy.findByTestId("object-detail");
+        cy.findAllByText("McClure-Lockman");
+
+        /**
+         * Helper function related to this test only!
+         */
+        function runQuestion({ question, sandboxValue } = {}) {
+          // Run the question
+          cy.visit(`/question/${question}?sandbox=${sandboxValue}`);
+          // Wait for results
+          cy.wait("@cardQuery");
+        }
       });
 
       it("simple sandboxing should work (metabase#14629)", () => {
@@ -1036,7 +1006,7 @@ describeEE("formatting > sandboxes", () => {
       });
     });
 
-    it.skip("should be able to visit ad-hoc/dirty question when permission is granted to the linked table column, but not to the linked table itself (metabase#15105)", () => {
+    it("should be able to visit ad-hoc/dirty question when permission is granted to the linked table column, but not to the linked table itself (metabase#15105)", () => {
       cy.sandboxTable({
         table_id: ORDERS_ID,
         attribute_remappings: {
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj
index 39653df98505a49d693075b95ee4823487e99007..5d7ff356c83bc4f1220c2898ddb1db1e54569b7c 100644
--- a/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj
@@ -23,6 +23,7 @@
    [metabase.query-processor.error-type :as qp.error-type]
    ^{:clj-kondo/ignore [:deprecated-namespace]}
    [metabase.query-processor.middleware.fetch-source-query-legacy :as fetch-source-query-legacy]
+   [metabase.query-processor.pipeline :as qp.pipeline]
    [metabase.query-processor.store :as qp.store]
    [metabase.server.middleware.session :as mw.session]
    [metabase.util :as u]
@@ -173,12 +174,16 @@
 
 (mu/defn- native-query-metadata :- [:+ :map]
   [source-query :- [:map [:source-query :any]]]
-  (let [result (mw.session/as-admin
-                 ((requiring-resolve 'metabase.query-processor/process-query)
-                  {:database (u/the-id (lib.metadata/database (qp.store/metadata-provider)))
-                   :type     :query
-                   :query    {:source-query source-query
-                              :limit        0}}))]
+  (let [result
+        ;; Rebind *result* in case the outer query is being streamed back to the client. The streaming code binds this
+        ;; to a custom handler, and we don't want to accidentally terminate the stream here!
+        (binding [qp.pipeline/*result* qp.pipeline/default-result-handler]
+          (mw.session/as-admin
+            ((requiring-resolve 'metabase.query-processor/process-query)
+             {:database (u/the-id (lib.metadata/database (qp.store/metadata-provider)))
+              :type     :query
+              :query    {:source-query source-query
+                         :limit        0}})))]
     (or (-> result :data :results_metadata :columns not-empty)
         (throw (ex-info (tru "Error running query to determine metadata")
                         {:source-query source-query
@@ -196,17 +201,17 @@
   (let [table-metadata   (original-table-metadata table-id)
         ;; make sure source query has `:source-metadata`; add it if needed
         [metadata save?] (cond
-                          ;; if it already has `:source-metadata`, we're good to go.
+                           ;; if it already has `:source-metadata`, we're good to go.
                            (seq source-metadata)
                            [source-metadata false]
 
-                          ;; if it doesn't have source metadata, but it's an MBQL query, we can preprocess the query to
-                          ;; get the expected metadata.
+                           ;; if it doesn't have source metadata, but it's an MBQL query, we can preprocess the query to
+                           ;; get the expected metadata.
                            (not (get-in source-query [:source-query :native]))
                            [(mbql-query-metadata source-query) true]
 
-                          ;; otherwise if it's a native query we'll have to run the query really quickly to get the
-                          ;; expected metadata.
+                           ;; otherwise if it's a native query we'll have to run the query really quickly to get the
+                           ;; expected metadata.
                            :else
                            [(native-query-metadata source-query) true])
         metadata (reconcile-metadata metadata table-metadata)]
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
index 6aa1df3381fc874e59ae21ddda4e23b8c98189df..a5f7a310a66d8c9abec23174a4111f36ca30a07d 100644
--- a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
@@ -28,6 +28,7 @@
    [metabase.query-processor.pivot :as qp.pivot]
    [metabase.query-processor.preprocess :as qp.preprocess]
    [metabase.query-processor.store :as qp.store]
+   [metabase.query-processor.streaming.test-util :as streaming.test-util]
    [metabase.query-processor.util :as qp.util]
    [metabase.query-processor.util.add-alias-info :as add]
    [metabase.server.middleware.session :as mw.session]
@@ -1222,3 +1223,16 @@
                       :attributes {"cat" 50}}
       (data-perms/set-table-permission! &group (mt/id :people) :perms/view-data :blocked)
       (is (= 10 (count (mt/rows (qp/process-query (mt/mbql-query venues)))))))))
+
+(deftest native-sandbox-no-query-metadata-streaming-test
+  (testing "A sandbox powered by a native query source card can be used via the streaming API even if the card has no
+           stored results_metadata (#49985)"
+    (met/with-gtaps! {:gtaps      {:venues (venues-category-native-gtap-def)}
+                      :attributes {"cat" 50}}
+      (let [sandbox-card-id (t2/select-one-fn :card_id
+                                              :model/GroupTableAccessPolicy
+                                              :group_id (:id &group)
+                                              :table_id (mt/id :venues))]
+        (is (nil? (t2/select-one-fn :result_metadata :model/Card sandbox-card-id)))
+        (is (= 10 (count (mt/rows (streaming.test-util/process-query-basic-streaming :api (mt/mbql-query venues))))))
+        (is (not (nil? (t2/select-one-fn :result_metadata :model/Card sandbox-card-id))))))))
diff --git a/src/metabase/query_processor/pipeline.clj b/src/metabase/query_processor/pipeline.clj
index 1fa230c08a240d0732ddba4744153139aac0af09..4f8cf0799cd7e4e9a0c66e56e6fcf653d0180218 100644
--- a/src/metabase/query_processor/pipeline.clj
+++ b/src/metabase/query_processor/pipeline.clj
@@ -23,14 +23,19 @@
   []
   (some-> *canceled-chan* a/poll!))
 
-(defn ^:dynamic *result*
-  "Called exactly once with the final result, which is the result of either [[*reduce*]] (if query completed
-  successfully), or an Exception (if it did not)."
+(defn default-result-handler
+  "Default implementation for *result*."
   [result]
   (if (instance? Throwable result)
     (throw result)
     result))
 
+(defn ^:dynamic *result*
+  "Called exactly once with the final result, which is the result of either [[*reduce*]] (if query completed
+  successfully), or an Exception (if it did not)."
+  [result]
+  (default-result-handler result))
+
 (defn ^:dynamic *execute*
   "Called by [[*run*]] to have driver run query. By default, [[metabase.driver/execute-reducible-query]]. `respond` is a
   callback with the signature:
diff --git a/src/metabase/query_processor/streaming.clj b/src/metabase/query_processor/streaming.clj
index b5342a843172c43bbc56900f17a1f8117084dc73..310ad1324afadd80d149ffd47e43d5bce223f08e 100644
--- a/src/metabase/query_processor/streaming.clj
+++ b/src/metabase/query_processor/streaming.clj
@@ -136,18 +136,17 @@
 (mu/defn- streaming-result-fn :- fn?
   [results-writer   :- (lib.schema.common/instance-of-class metabase.query_processor.streaming.interface.StreamingResultsWriter)
    ^OutputStream os :- (lib.schema.common/instance-of-class OutputStream)]
-  (let [orig qp.pipeline/*result*]
-    (fn result [result]
-      (when (= (:status result) :completed)
-        (log/debug "Finished writing results; closing results writer.")
-        (try
-          (qp.si/finish! results-writer result)
-          (catch EofException e
-            (log/error e "Client closed connection prematurely")))
-        (u/ignore-exceptions
-          (.flush os)
-          (.close os)))
-      (orig result))))
+  (fn result [result]
+    (when (= (:status result) :completed)
+      (log/debug "Finished writing results; closing results writer.")
+      (try
+        (qp.si/finish! results-writer result)
+        (catch EofException e
+          (log/error e "Client closed connection prematurely")))
+      (u/ignore-exceptions
+        (.flush os)
+        (.close os)))
+    (qp.pipeline/default-result-handler result)))
 
 (defn do-with-streaming-rff
   "Context to pass to the QP to streaming results as `export-format` to an output stream. Can be used independently of