28 Commits

Author SHA1 Message Date
TJ Paulson df201f3ee4 fix: security review — dashboard XSS, private-mode federation gap, Matrix bridge resilience (#839)
* fix(web): escape untrusted mesh fields in dashboard to close DOM XSS

Security review found user-controlled mesh data reaching innerHTML unescaped
(upstream-originating; worth a PR back to l5yth/potato-mesh):

- High: channel_name was interpolated raw by formatChatChannelTag and rendered
  via innerHTML on the node-detail page and map overlays, so a crafted channel
  name (any radio can set one) executed script for every viewer. Now escaped
  with the canonical escapeHtml.
- Defense-in-depth: node_id, role, and hw_model in the node table are now
  escaped for consistency with sibling cells.

Regression test added for the channel-name escape. Full JS suite: 1448 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(web): gate POST /api/instances by federation mode + hardening

Security review findings in the Sinatra app (upstream-originating; worth a PR
back to l5yth/potato-mesh):

- High: POST /api/instances performed outbound federation fetches and DB writes
  with no federation/private guard, so a PRIVATE=1 or FEDERATION=0 node still
  acted on unsolicited signed announcements. Added `halt 404 unless
  federation_enabled?`, mirroring the existing GET /api/instances guard.
- Low: the JSON-LD <script> block now escapes </script> breakout characters.
- Low: federation peer fetches now enforce a max response size
  (REMOTE_INSTANCE_MAX_RESPONSE_BYTES, default 8 MiB) to bound memory.

Regression spec added for the federation-gate (private + FEDERATION=0 cases).

NOTE: rspec could not be run locally (this host has only Ruby 2.6; the project
needs >=3.0 and no Docker was available). Changes are `ruby -c` syntax-checked
and reviewed against the mirrored guard; the ruby.yml CI workflow must confirm
the spec on push/PR before merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(matrix): stop silent message loss + add HTTP timeouts + log reg failures

Security/resilience review of the Matrix bridge (upstream-originating; worth a
PR back to l5yth/potato-mesh):

- High: within a poll batch, a later message that forwarded successfully
  advanced the in-memory rx_time watermark past an EARLIER message that failed,
  so the failed message was never refetched -> silent permanent loss. poll_once
  now breaks at the first failure, committing only the contiguous successful
  prefix; the failed message and everything after it are retried in order next
  poll (no reordering, no duplicates). Regression test added and proven to bite.
- High: the shared reqwest client had no timeouts, so a hung homeserver/API
  stalled the single-threaded poll loop (and startup health checks) forever.
  Added timeout(30s) + connect_timeout(10s).
- Medium: ensure_user_registered swallowed all non-success responses silently;
  it now warns (status + body) on anything other than the expected
  already-registered case, so a bad as_token is diagnosable.

cargo test: 118 passed; cargo fmt clean. Tradeoff: a permanently-failing
message now blocks its batch (retried each poll) rather than being lost -- safer
than silent loss; a skip-after-N/dead-letter follow-up is possible if needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address Copilot review on the security PR (Matrix errcode + spec hygiene)

- matrix.rs: ensure_user_registered now treats only the specific M_USER_IN_USE
  errcode as "already registered", instead of any 400 Bad Request, so real 400
  failures (malformed request, config issues) reach the diagnostic warn branch.
  Updated the M_USER_IN_USE test to send the errcode body and added a test that
  a non-M_USER_IN_USE 400 is not suppressed. cargo test: 119 passing.
- app_spec.rb: the private-mode POST /api/instances example now restores
  ENV["PRIVATE"] via an around/ensure block (mirroring the FEDERATION pattern),
  so private mode can't leak into later specs and cause order-dependent failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: implement Copilot follow-ups — streaming size cap + bounded skip

- instance_fetcher.rb: perform_single_http_request now uses the block form of
  Net::HTTP#request + response.read_body to enforce the response-size cap
  INCREMENTALLY, aborting as soon as the limit is exceeded, instead of letting
  the non-block form buffer the whole body into memory first. Updated the two
  federation_spec mocks to the block/streaming form and added a size-cap test.
- main.rs (Matrix bridge): a permanently-failing ("poison") message no longer
  blocks the batch forever. Consecutive per-message failures are counted
  in-memory; after MAX_FORWARD_ATTEMPTS (5) the message is skipped (advanced
  past, with a warning) so later messages make progress. Transient failures
  still stop-and-retry in order (no silent loss, no reordering). Added a
  skip-after-N regression test; the original watermark test still passes.
  cargo test: 120 passing, cargo fmt clean.

Ruby: instance_fetcher.rb is `ruby -c` clean; the spec exercises the new paths
but rspec/`ruby -c` can't run locally (Ruby 2.6 here can't parse the repo's
3.1+ syntax) — the ruby.yml CI is the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(web): fix private-mode /api/instances spec to actually enable private mode

Running the suite locally (Ruby 4.0) revealed the private-mode POST /api/instances
example never entered private mode: the top-level `before` hook deletes
ENV["PRIVATE"] before each example, so toggling it (even via an around block)
had no effect and the request returned 201 instead of 404.

Switch to stubbing PotatoMesh::Config.private_mode_enabled? => true, the pattern
the rest of the suite uses for private-mode cases. This makes the test actually
exercise the guard AND removes the ENV manipulation entirely (so there is no
cross-spec ENV leak to worry about). Full web suite: 1470 examples, 0 failures;
rufo clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 22:27:37 +02:00
l5y 843dc85776 web: fix federation cycle and http response error handler (#810) 2026-06-22 17:06:56 +02:00
l5y be9e7c006f web: fix federation dns status 500 (#809) 2026-06-22 16:35:56 +02:00
l5y 027b6160df web: federation signature v2 migration (#805)
* web: federation signature v2 migration

* web: address review comments
2026-06-22 10:01:45 +02:00
l5y 5e0363a0ec web: breaking change on stats api (#801)
* web: breaking change on stats api

* address review comments
2026-06-21 13:41:41 +02:00
l5y d2fe4f8223 web: add node opt-out marker and data retention policies (#793)
* web: add node opt-out marker and data retention policies

* web: address review comments

* web: address review comments
2026-05-20 21:02:59 +02:00
l5y 73e161f432 web: fix liveliness of api data hydration bug (#783)
* web: fix liveliness of api data hydration bug

* web: address review comments
2026-05-03 13:05:37 +02:00
l5y 0fbff32535 web: refactor 2/7 federation (#773)
* web: refactor 2/7 federation

* web: close federation coverage gaps and apply review nits

Address Codecov patch coverage feedback by adding rspec examples for
the 51 lines flagged across the new federation shards (announce,
crawl, validation, http_client, self_instance, instance_metrics,
announcer_threads, lifecycle, signature). Per-shard line coverage in
the federation directory is now 100%.

Apply two review-comment changes: rename the awkwardly-named
http_client_get.rb to instance_fetcher.rb (matching its semantic
role rather than the HTTP verb), and declare PotatoMesh::App::Federation
explicitly in the federation.rb manifest so the namespace is owned by
this file rather than implicitly created by whichever shard happens to
load first.
2026-05-02 22:12:20 +02:00
l5y 580c2fb6ea web: fix federation node counts (#749)
* web: fix federation node counts

* web: fix federation node counts

* web: address review comments
2026-04-15 15:43:34 +02:00
l5y 07c8e85caa web: fix federation resolver issue with multi addresses (#743)
* web: fix federation resolver issue with multi addresses

* web: add tests

* web: address review comments
2026-04-14 18:55:40 +02:00
l5y b951dbffeb web: per protocol active node counts (#735)
* web: per protocol active node counts

* web: address review comments
2026-04-13 18:26:16 +02:00
l5y 9c3dae3e7d chore: refactor codebase before meshcore release (#682)
* chore: refactor codebase before meshcore release

* data: run black

* fix: resolve SonarCloud S1244/S5796 reliability issues in test files

Replace floating-point equality comparisons with pytest.approx() to
satisfy S1244, and replace the `is` identity operator with id()-based
comparison to satisfy S5796.

* fix: remove duplicate encrypted_flag assignment in store_packet_dict

The encrypted_flag was computed identically on lines 307 and 345 with no
mutation of `encrypted` between them. Remove the dead second assignment.
2026-04-04 10:22:31 +02:00
l5y 5b0a6f5f8b web: expose node stats in distinct api (#641)
* web: expose node stats in distinct api

* web: address review comments

* web: address review comments

* web: address review comments

* web: address review comments
2026-02-14 21:14:10 +01:00
l5y 5c5a9df5a6 federation: ensure requests timeout properly and can be terminated (#631)
* federation: ensure requests timeout properly and can be terminated

* web: address review comments

* web: address review comments

* web: address review comments

* web: address review comments
2026-02-14 12:29:01 +01:00
l5y 96b62d7e14 web: fix limit when counting remote nodes (#547) 2025-12-14 15:05:19 +01:00
l5y baf6ffff0b web: improve instances map and table view (#546)
* web: improve instances map and table view

* web: address review comments

* run rufo
2025-12-14 14:35:55 +01:00
l5y 8181fc8e03 web: further refine the federation page (#534)
* web: further refine the federation page

* web: address review comments

* web: address review comments
2025-12-04 13:31:23 +01:00
l5y 2bb8e3fd66 Chore: update license headers (#424) 2025-11-08 10:41:57 +01:00
l5y 487d618e00 Parallelize federation tasks with worker pool (#419)
* Parallelize federation work with worker pool

* Handle worker pool shutdown fallback during federation announcements
2025-11-07 17:24:37 +01:00
l5y 61e8c92f62 Harden federation announcements (#381) 2025-10-18 10:38:28 +02:00
l5y a6ba9a8227 Guard federation DNS resolution against restricted networks (#362)
* Guard federation DNS resolution against restricted networks

* Pin federation HTTP clients to vetted IPs
2025-10-16 21:15:34 +02:00
l5y 7055444c4b Add federation ingestion limits and tests (#364) 2025-10-16 21:15:18 +02:00
l5y b2f4fcaaa5 Gracefully retry federation announcements over HTTP (#355) 2025-10-15 23:11:59 +02:00
l5y a32125996c Remove federation timeout environment overrides (#352) 2025-10-15 20:04:19 +02:00
l5y db7b67d859 Improve federation instance error diagnostics (#350) 2025-10-15 18:35:22 +02:00
l5y ee904633a8 Handle CRL lookup failures during federation TLS (#299) 2025-10-12 09:56:53 +02:00
l5y f04e917cd9 Add Apache license headers to missing sources (#297) 2025-10-12 09:38:04 +02:00
l5y aba94b197d Handle federation HTTPS CRL verification failures (#293) 2025-10-12 09:22:54 +02:00