- /u/:callsign profile no longer leaks private contacts or pending beacons; Radio.list_contacts_for_user/2 and Beacons.list_beacons_for_user/2 now filter by viewer (owner/admin see everything, others see only public). - Radio.create_contact/2 places user_id on the struct before submission_changeset so validate_user_or_email/1 accepts authenticated submissions that omit submitter_email. - valkey_test.exs runs async: false; its setup mutates global Application env and Process registry, which otherwise crashed concurrent ConnCase GridCache.clear/0 calls (Mox.UnexpectedCallError on SCAN).
4.9 KiB
Bugs Found 2026-05-12
All three fixed; mix test --seed 949374 now passes (3893 tests, 0 failures).
1. Public profile leaks private contacts and pending beacons [FIXED]
Severity: High
Category: Privacy / authorization bypass
Files:
lib/microwaveprop_web/live/user_profile_live.ex:21lib/microwaveprop_web/live/user_profile_live.ex:22lib/microwaveprop_web/live/user_profile_live.ex:92lib/microwaveprop_web/live/user_profile_live.ex:130lib/microwaveprop/beacons.ex:61
/u/:callsign is mounted in the public LiveView session, but it loads
Radio.list_contacts_for_user(user) and Beacons.list_beacons_for_user(user)
without checking whether the visitor is the profile owner or an admin.
list_contacts_for_user/1 returns every submitted contact for that user,
including contacts where private == true. The profile template then renders
those rows and links to /contacts/:id. The contact detail page correctly
404s for unauthorized viewers, but the public profile has already exposed the
contact timestamp, stations, band, mode, and distance.
list_beacons_for_user/1 explicitly returns approved and pending beacons for
the public profile page. The profile template renders pending beacon callsign,
frequency, grid, keying, status, and link. This contradicts the beacon visibility
model where pending beacons should only be visible to the submitter and admins.
Suggested fix: split the profile query by viewer. Anonymous/non-owner visitors should see only non-private contacts and approved beacons. Owners/admins can see their private contacts and pending beacons. Add LiveView tests for anonymous, owner, and admin views.
2. Radio.create_contact/2 rejects authenticated submissions unless callers also pass an email [FIXED]
Severity: Medium
Category: Broken context contract / validation ordering
Files:
lib/microwaveprop/radio.ex:629lib/microwaveprop/radio.ex:632lib/microwaveprop/radio.ex:633lib/microwaveprop/radio/contact.ex:109lib/microwaveprop/radio/contact.ex:173
Radio.create_contact(attrs, user_id) adds user_id after
Contact.submission_changeset/2 has already run validate_user_or_email/1.
That validation only sees fields cast from attrs, so a valid authenticated
create with user_id but no submitter_email returns an invalid changeset with
submitter_email: can't be blank.
Confirmed with:
Radio.create_contact(valid_attrs_without_submitter_email, user.id)
#=> {:error, %Ecto.Changeset{errors: [submitter_email: {"can't be blank", []}]}}
Current web/API call sites mask this by inserting the user's email into params
before calling the context, but the public function's contract and implementation
disagree. Any future trusted caller that relies on the user_id argument alone
will fail unexpectedly.
Suggested fix: put user_id into the struct or attrs before calling
Contact.submission_changeset/2, or move the ownership validation to after
maybe_put_user_id/2. Add a context-level regression test that passes
user_id without submitter_email.
3. Full test suite is order-dependent around Valkey-backed GridCache.clear/0 [FIXED]
Severity: Medium
Category: Test isolation / flaky suite
Files:
test/support/conn_case.ex:45test/microwaveprop/weather/grid_cache_valkey_test.exs:26test/microwaveprop/weather/grid_cache_valkey_test.exs:35lib/microwaveprop/weather/grid_cache.ex:477
One mix test run failed with 3 setup failures. ConnCase calls
GridCache.clear/0 for every web test. If a Valkey cache test has registered
Microwaveprop.Valkey.Conn and swapped :valkey_adapter, GridCache.clear/0
takes the Valkey path and calls Valkey.scan_match/1. In the failed run, that
reached Microwaveprop.Valkey.MockAdapter.command/3 without a SCAN
expectation, crashing unrelated web tests during setup.
Failure shape:
Mox.UnexpectedCallError: no expectation defined for
Microwaveprop.Valkey.MockAdapter.command/3
args: [Microwaveprop.Valkey.Conn, ["SCAN", "0", "MATCH", "prop:wg:*", "COUNT", "500"], ...]
Verification:
mix test --seed 949374
# 3880 tests, 3 failures, 6 skipped
mix precommit
# 3880 tests, 0 failures, 6 skipped
The subsequent passing mix precommit run makes this look order-dependent
rather than a deterministic failure.
Suggested fix: make Valkey test setup restore global process/env state before
other tests can observe it, or avoid global Process.register/2 for Conn.
Also consider making ConnCase force the ETS fallback or installing a default
Valkey mock stub for GridCache.clear/0.
Verification Notes
mix compile --warnings-as-errorspassed.mix credo --strictreported design/refactoring/style issues only; no bug-class findings were added from Credo.mix test --seed 949374failed as described in bug 3.mix precommitpassed afterward, confirming the checked-in code still clears the normal project gate for this run.