From f2b359a62a0f36e8b4b3feca926effcdcca2406d Mon Sep 17 00:00:00 2001 From: Homer Quan Date: Mon, 18 May 2026 13:41:57 -0400 Subject: [PATCH] Harden recovery bundle path loading --- lib/mirror_neuron/cluster/leader.ex | 13 +++++++++---- lib/mirror_neuron/job_bundle.ex | 15 +++++++++++++++ lib/mirror_neuron/runtime/local_recovery.ex | 4 ++-- tests/unit/job_bundle_test.exs | 21 +++++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/mirror_neuron/cluster/leader.ex b/lib/mirror_neuron/cluster/leader.ex index c360f881..faa72e6e 100644 --- a/lib/mirror_neuron/cluster/leader.ex +++ b/lib/mirror_neuron/cluster/leader.ex @@ -279,13 +279,18 @@ defmodule MirrorNeuron.Cluster.Leader do cond do is_binary(fingerprint) and fingerprint != "" -> case MirrorNeuron.Bundle.Archive.load(fingerprint) do - {:ok, bundle} -> {:ok, bundle} - {:error, _reason} when is_binary(job_path) -> MirrorNeuron.JobBundle.load(job_path) - {:error, reason} -> {:error, reason} + {:ok, bundle} -> + {:ok, bundle} + + {:error, _reason} when is_binary(job_path) -> + MirrorNeuron.JobBundle.load_filesystem_path(job_path) + + {:error, reason} -> + {:error, reason} end is_binary(job_path) -> - MirrorNeuron.JobBundle.load(job_path) + MirrorNeuron.JobBundle.load_filesystem_path(job_path) true -> {:error, :missing_bundle_reference} diff --git a/lib/mirror_neuron/job_bundle.ex b/lib/mirror_neuron/job_bundle.ex index 032734a3..4424206c 100644 --- a/lib/mirror_neuron/job_bundle.ex +++ b/lib/mirror_neuron/job_bundle.ex @@ -38,6 +38,21 @@ defmodule MirrorNeuron.JobBundle do end end + def load_filesystem_path(path) when is_binary(path) do + expanded = Path.expand(path) + + cond do + File.dir?(expanded) -> + load_from_directory(expanded) + + File.exists?(expanded) -> + {:error, "expected a job folder, got file #{expanded}"} + + true -> + {:error, "job folder does not exist: #{expanded}"} + end + end + defp load_from_directory(root_path) do manifest_path = Path.join(root_path, "manifest.json") payloads_path = Path.join(root_path, "payloads") diff --git a/lib/mirror_neuron/runtime/local_recovery.ex b/lib/mirror_neuron/runtime/local_recovery.ex index f2dd2665..34b10e69 100644 --- a/lib/mirror_neuron/runtime/local_recovery.ex +++ b/lib/mirror_neuron/runtime/local_recovery.ex @@ -258,12 +258,12 @@ defmodule MirrorNeuron.Runtime.LocalRecovery do is_binary(fingerprint) and fingerprint != "" -> case Archive.load(fingerprint) do {:ok, bundle} -> {:ok, bundle} - {:error, _reason} when is_binary(job_path) -> JobBundle.load(job_path) + {:error, _reason} when is_binary(job_path) -> JobBundle.load_filesystem_path(job_path) {:error, _reason} -> load_embedded_manifest(job) end is_binary(job_path) -> - JobBundle.load(job_path) + JobBundle.load_filesystem_path(job_path) true -> load_embedded_manifest(job) diff --git a/tests/unit/job_bundle_test.exs b/tests/unit/job_bundle_test.exs index 0116a52c..6bced45e 100644 --- a/tests/unit/job_bundle_test.exs +++ b/tests/unit/job_bundle_test.exs @@ -42,6 +42,27 @@ defmodule MirrorNeuron.JobBundleTest do assert {:error, "unexpected byte" <> _} = JobBundle.load("invalid json string {") end + test "load_filesystem_path/1 rejects inline json strings" do + json_str = Jason.encode!(@valid_manifest_map) + + assert {:error, "job folder does not exist: " <> _} = JobBundle.load_filesystem_path(json_str) + end + + test "load_filesystem_path/1 with valid directory structure" do + dir = "test_bundle_dir_filesystem_only" + File.mkdir_p!(dir) + File.write!(Path.join(dir, "manifest.json"), Jason.encode!(@valid_manifest_map)) + File.mkdir_p!(Path.join(dir, "payloads")) + + on_exit(fn -> File.rm_rf!(dir) end) + + assert {:ok, bundle} = JobBundle.load_filesystem_path(dir) + assert bundle.root_path == Path.expand(dir) + assert bundle.manifest_path == Path.join(Path.expand(dir), "manifest.json") + assert bundle.payloads_path == Path.join(Path.expand(dir), "payloads") + assert bundle.manifest.graph_id == "test_graph" + end + test "load/1 with path to regular file instead of dir returns error" do # Create a dummy file path = "dummy_file.json"