From 1da1cbed439e53f9744a178c0f7d2cad9af2d5b5 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 2 Apr 2026 12:38:49 -0500 Subject: [PATCH] Fix nil access bugs found in security/bug audit - TerrainProfileWorker: guard pos1/pos2 nil before accessing lat/lon, mark unavailable if missing instead of crashing - Propagation analysis: guard pos1 nil in compute_factors - contact_path_points: return [] instead of [{nil, nil}] on missing coords - JS elevation hook: null check canvas before getContext --- assets/js/elevation_profile_hook.js | 1 + lib/microwaveprop/radio.ex | 17 +++++++++++------ .../workers/terrain_profile_worker.ex | 18 ++++++++++++------ .../live/contact_live/show.ex | 2 ++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/assets/js/elevation_profile_hook.js b/assets/js/elevation_profile_hook.js index c1ba6a87..311f7b89 100644 --- a/assets/js/elevation_profile_hook.js +++ b/assets/js/elevation_profile_hook.js @@ -90,6 +90,7 @@ export const ElevationProfile = { } const canvas = this.el.querySelector("canvas") + if (!canvas) return const ctx = canvas.getContext("2d") this.chart = new Chart(ctx, { diff --git a/lib/microwaveprop/radio.ex b/lib/microwaveprop/radio.ex index 562d11ad..52e0cfa3 100644 --- a/lib/microwaveprop/radio.ex +++ b/lib/microwaveprop/radio.ex @@ -153,12 +153,17 @@ defmodule Microwaveprop.Radio do lat2 = pos2["lat"] lon2 = pos2["lon"] || pos2["lng"] - if lat1 && lon1 && lat2 && lon2 do - mid_lat = (lat1 + lat2) / 2 - mid_lon = (lon1 + lon2) / 2 - [{lat1, lon1}, {mid_lat, mid_lon}, {lat2, lon2}] - else - [{lat1, lon1}] + cond do + lat1 && lon1 && lat2 && lon2 -> + mid_lat = (lat1 + lat2) / 2 + mid_lon = (lon1 + lon2) / 2 + [{lat1, lon1}, {mid_lat, mid_lon}, {lat2, lon2}] + + lat1 && lon1 -> + [{lat1, lon1}] + + true -> + [] end end diff --git a/lib/microwaveprop/workers/terrain_profile_worker.ex b/lib/microwaveprop/workers/terrain_profile_worker.ex index 707ee2bf..bf7193f4 100644 --- a/lib/microwaveprop/workers/terrain_profile_worker.ex +++ b/lib/microwaveprop/workers/terrain_profile_worker.ex @@ -19,12 +19,13 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do :ok else contact = Radio.get_contact!(qso_id) - lat1 = contact.pos1["lat"] - lon1 = contact.pos1["lon"] || contact.pos1["lng"] - lat2 = contact.pos2["lat"] - lon2 = contact.pos2["lon"] || contact.pos2["lng"] - dist_km = Decimal.to_float(contact.distance_km) - freq_ghz = Decimal.to_float(contact.band) / 1000 + + with %{"lat" => lat1} <- contact.pos1, + lon1 when is_number(lon1) <- contact.pos1["lon"] || contact.pos1["lng"], + %{"lat" => lat2} <- contact.pos2, + lon2 when is_number(lon2) <- contact.pos2["lon"] || contact.pos2["lng"] do + dist_km = Decimal.to_float(contact.distance_km || Decimal.new(0)) + freq_ghz = Decimal.to_float(contact.band) / 1000 # Look up HRRR refractivity gradient for dynamic k-factor k = lookup_k_factor(contact) @@ -69,6 +70,11 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do {:error, reason} -> {:error, reason} end + else + _ -> + Radio.set_enrichment_status!([qso_id], :terrain_status, :unavailable) + :ok + end end end diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index bcd98ab4..32bd1fcc 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -1105,6 +1105,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do {nil, [], nil} end + defp compute_factors(_hrrr, %{pos1: nil}, _band_config), do: {nil, [], nil} + defp compute_factors(hrrr, contact, band_config) do lon = contact.pos1["lon"] || contact.pos1["lng"] temp_c = hrrr.surface_temp_c