VYPR
Medium severityNVD Advisory· Published May 26, 2026

CVE-2026-48592

CVE-2026-48592

Description

Missing Authorization vulnerability in oban-bg oban_web ('Elixir.Oban.Web.Jobs.DetailComponent' modules) allows unauthorized job worker substitution.

The handle_event("save-job", ...) handler in 'Elixir.Oban.Web.Jobs.DetailComponent' does not perform an authorization check, unlike the sibling cancel, delete, and retry handlers which all verify the caller's privileges via can?/2. An authenticated user with :read_only access can push a forged save-job LiveView WebSocket event to overwrite a job's worker field with any other existing Oban.Worker module in the application. On the job's next execution attempt, Oban will invoke perform/1 on the attacker-chosen module instead of the intended one.

This issue affects oban_web: from 2.12.0 before 2.12.5.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Missing authorization in oban_web's save-job handler allows authenticated read-only users to replace a job's worker module with any other Oban.Worker.

Vulnerability

The handle_event("save-job", ...) handler in the Elixir.Oban.Web.Jobs.DetailComponent LiveView module ([1], [4]) does not perform an authorization check, unlike sibling handlers for cancel, delete, and retry which all verify privileges via can?/2. An authenticated user with :read_only access can push a forged save-job LiveView WebSocket event to overwrite a job's worker field with any other existing Oban.Worker module in the application. This affects oban_web versions from 2.12.0 before 2.12.5 [1][3].

Exploitation

The attacker must have an authenticated session with at minimum :read_only access to the Oban.Web dashboard, which must be deployed and accessible to users with less than full job-management privileges [3][4]. The attacker opens any job's detail panel to obtain the job ID, then pushes a forged save-job event over the LiveView WebSocket with the worker parameter set to a desired target module name [4]. The Phoenix LiveView channel dispatches the event regardless of DOM state, so the attacker does not need to interact with the UI [4]. The attacker is constrained to substituting an existing Oban.Worker module already loaded in the application—no code injection is possible [4].

Impact

On the job's next execution attempt, Oban invokes perform/1 on the attacker-chosen module instead of the intended one [1][4]. The CVSS 4.0 score is 5.3 (Medium) with vector CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N [1][3]. The actual impact depends on what Oban.Worker modules are available in the target application [4]. Potential consequences include unauthorized job execution that could lead to information disclosure, data modification, or other unintended behavior based on the substituted worker's functionality.

Mitigation

The fix is included in oban_web version 2.12.5, released 2026-05-26 [1][2]. The patch adds an authorization check via can?(:update_jobs, @access) in both the fieldset disabled attribute and the save-job event handler, and introduces a new :update_jobs fine-grained action in the configuration [2]. Users of oban_web 2.12.0 through 2.12.4 should upgrade to 2.12.5 or later. If immediate upgrade is not possible, restrict access to the Oban.Web dashboard to trusted administrators only, as a mitigating control.

AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Oban Bg/Oban Webinferred2 versions
    >=2.12.0,<2.12.5+ 1 more
    • (no CPE)range: >=2.12.0,<2.12.5
    • (no CPE)range: >=2.12.0 <2.12.5

Patches

1
ab3c5d1d3eba

Merge commit from fork

https://github.com/oban-bg/oban_webParker SelbertMay 26, 2026via body-scan
4 files changed · +33 9
  • guides/advanced/limiting_access.md+1 0 modified
    @@ -101,6 +101,7 @@ The available fine-grained actions are:
     | `:scale_queues`     | Change queue concurrency               |
     | `:stop_queues`      | Stop queues entirely                   |
     | `:update_crons`     | Edit cron configuration                |
    +| `:update_jobs`      | Edit job fields like worker or args    |
     
     Actions not listed in the keyword list are considered disabled. For example, this configuration
     allows job management but prevents any queue operations:
    
  • lib/oban/web/live/jobs/detail_component.ex+11 9 modified
    @@ -503,7 +503,7 @@ defmodule Oban.Web.Jobs.DetailComponent do
             </button>
     
             <div id="edit-content" class={["mt-3", if(executing?(@job), do: "hidden")]}>
    -          <fieldset disabled={executing?(@job)}>
    +          <fieldset disabled={executing?(@job) or not can?(:update_jobs, @access)}>
                 <form
                   id="job-edit-form"
                   class="grid grid-cols-4 gap-4 bg-gray-50 dark:bg-gray-800 rounded-md p-4"
    @@ -571,7 +571,7 @@ defmodule Oban.Web.Jobs.DetailComponent do
                     </button>
                     <button
                       type="submit"
    -                  disabled={not @edit_changed?}
    +                  disabled={not @edit_changed? or not can?(:update_jobs, @access)}
                       class="px-6 py-2 bg-blue-500 text-white text-sm font-medium rounded-md hover:bg-blue-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
                     >
                       Save Changes
    @@ -813,14 +813,16 @@ defmodule Oban.Web.Jobs.DetailComponent do
       def handle_event("save-job", params, socket) do
         job = socket.assigns.job
     
    -    changes =
    -      params
    -      |> parse_edit_params(job)
    -      |> Enum.reject(fn {_key, val} -> is_nil(val) end)
    -      |> Map.new()
    +    if can?(:update_jobs, socket.assigns.access) do
    +      changes =
    +        params
    +        |> parse_edit_params(job)
    +        |> Enum.reject(fn {_key, val} -> is_nil(val) end)
    +        |> Map.new()
     
    -    if map_size(changes) > 0 do
    -      send(self(), {:update_job, job, changes})
    +      if map_size(changes) > 0 do
    +        send(self(), {:update_job, job, changes})
    +      end
         end
     
         {:noreply, assign(socket, edit_changed?: false)}
    
  • lib/oban/web/resolver.ex+2 0 modified
    @@ -145,6 +145,7 @@ defmodule Oban.Web.Resolver do
               | :scale_queues
               | :stop_queues
               | :update_crons
    +          | :update_jobs
     
       @type qualifier :: :args | :meta | :nodes | :queues | :tags | :workers
     
    @@ -319,6 +320,7 @@ defmodule Oban.Web.Resolver do
       * `:scale_queues`
       * `:stop_queues`
       * `:update_crons`
    +  * `:update_jobs`
     
       Actions which aren't listed are considered disabled.
     
    
  • test/oban/web/pages/jobs/detail_test.exs+19 0 modified
    @@ -115,6 +115,25 @@ defmodule Oban.Web.Pages.Jobs.DetailTest do
           assert render(live) =~ "Job updated successfully"
         end
     
    +    test "read-only users cannot submit edits or rewrite the worker" do
    +      job = insert_job!([ref: 1], state: "available", worker: WorkerA)
    +
    +      {:ok, live, _html} = live(build_conn(), "/oban-readonly")
    +
    +      open_state(live, "available")
    +      open_details(live, job)
    +
    +      assert has_element?(live, "fieldset[disabled] #job-edit-form")
    +
    +      live
    +      |> form("#job-edit-form", %{"worker" => "Attacker.Worker", "args" => "{}"})
    +      |> render_submit()
    +
    +      reloaded = Repo.reload!(job)
    +      assert reloaded.worker == job.worker
    +      refute render(live) =~ "Job updated successfully"
    +    end
    +
         test "updating unrelated fields does not change scheduled_at", %{live: live} do
           scheduled_at = DateTime.utc_now() |> DateTime.add(3600) |> DateTime.truncate(:second)
     
    

Vulnerability mechanics

Root cause

"Missing authorization check in `handle_event("save-job", ...)` allows any authenticated user to overwrite a job's worker field."

Attack vector

An authenticated user with at minimum `:read_only` access can push a forged `save-job` LiveView WebSocket event to overwrite a job's `worker` field with any other existing `Oban.Worker` module in the application [ref_id=2]. The Phoenix LiveView channel dispatches any `phx-event` pushed over the authenticated WebSocket regardless of DOM state, so the attacker bypasses the disabled UI controls [ref_id=2]. On the job's next execution attempt, Oban invokes `perform/1` on the attacker-chosen module instead of the intended one [ref_id=2]. The attacker is constrained to substituting an existing `Oban.Worker` module already loaded in the application (no code injection) [ref_id=2].

Affected code

The vulnerability resides in `lib/oban/web/live/jobs/detail_component.ex` within the `handle_event("save-job", params, socket)` handler. Unlike the sibling `cancel`, `delete`, and `retry` handlers, this handler performs no authorization check via `can?/2` before dispatching `{:update_job, job, changes}` to the parent LiveView [patch_id=2590842]. The patch also adds `:update_jobs` as a new fine-grained permission atom in `lib/oban/web/resolver.ex` [patch_id=2590842].

What the fix does

The patch wraps the changes-building and dispatch logic inside `handle_event("save-job", ...)` with a guard: `if can?(:update_jobs, socket.assigns.access) do ... end` [patch_id=2590842]. It also disables the edit fieldset and submit button in the template when the user lacks `:update_jobs` permission by adding `or not can?(:update_jobs, @access)` to both `disabled` attributes [patch_id=2590842]. A new `:update_jobs` fine-grained permission atom is introduced in `lib/oban/web/resolver.ex` and documented in the access-limiting guide [patch_id=2590842]. The test confirms that a read-only user's submitted edit is silently ignored and the worker field remains unchanged [patch_id=2590842].

Preconditions

  • authAuthenticated session with at minimum :read_only access to the Oban.Web dashboard
  • configThe target application must be running oban_web >= 2.12.0 and < 2.12.5
  • inputThe attacker must know or discover a job ID to target

Reproduction

1. Obtain an authenticated session with at minimum `:read_only` access to the Oban.Web dashboard [ref_id=2]. 2. Open any job's detail panel to obtain its job ID [ref_id=2]. 3. Push a forged `save-job` event over the LiveView WebSocket with `"worker"` set to the desired target module name [ref_id=2]. 4. The server accepts the payload and updates the job row. On its next execution attempt, Oban invokes `perform/1` on the attacker-chosen module [ref_id=2].

Generated on May 26, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.