Skip to content

privacy: blacklisted nodes appear in /api/analytics/topology #91

Description

@dborup

Summary

GET /api/analytics/topology returns nodes listed in nodeBlacklist. The response filter exists but never removes anything, so a blacklisted node's pubkey and name remain in the topology analytics.

Root cause

filterBlacklistedFromTopology (cmd/server/routes.go) type-asserts each part of the response to a typed slice: []TopRepeater, []TopPair, []BestPathEntry, []MultiObsNode and map[string]*ObserverReach.

computeAnalyticsTopology (cmd/server/store.go) builds those parts as []map[string]interface{} and map[string]interface{}. Every assertion therefore fails, each branch is silently skipped, and the data is returned unfiltered. No server test covers the blacklist on this route.

A related hazard: the handler receives the shared cached object, either the steady-state recomputer snapshot or topoCache, and the filter writes its result back into that map. A filter that did match would modify the cached data in place for every later request and race with concurrent readers.

Affected endpoint and fields

/api/analytics/topology, both the default steady-state snapshot and the region/area/window queries. Five parts carry node pubkeys:

  • topRepeaters[].pubkey
  • topPairs[].pubkeyA / topPairs[].pubkeyB
  • bestPathList[].pubkey
  • multiObsNodes[].pubkey
  • perObserverReach{}.rings[].nodes[].pubkey

The same entries also carry the node's name.

Privacy impact

An operator who blacklists a node expects it to be hidden from the API. It stays hidden on the node endpoints, but the topology analytics still reveal its pubkey, name, repeater rank, pairings and per-observer reachability.

Reproduction

  1. Pick a repeater that appears in /api/analytics/topology.
  2. Add its pubkey to nodeBlacklist and restart.
  3. The node is gone from /api/nodes/<pubkey>, but /api/analytics/topology still lists it in the parts above.

The QA script qa/scripts/blacklist-test.sh reports this as hide-failed: /api/analytics/topology lists the blacklisted pubkey when the test node is present in the topology. A handler test over data built by computeAnalyticsTopology, with a node that is present in all five parts before blacklisting, fails on current master (1ee44d72): the node is still present in every part afterwards.

Expected behaviour (fail closed)

  • A blacklisted node is absent from all five parts. It is matched the way every other privacy check matches (Config.IsBlacklisted: trimmed, case-insensitive). A pair is dropped if either side is blacklisted.
  • The existing name-based hiding (HiddenNamePrefixes) keeps applying to the same entries.
  • A part or entry the filter cannot interpret (unknown shape, pubkey of an unexpected type) is dropped, never passed through.
  • The shared cached topology is never modified. Filtering works on a copy, and a blacklist change takes effect on the next response without stale data.
  • Responses for nodes that are not blacklisted are unchanged, and the topology computation itself is untouched.

Acceptance criteria

  • A handler test over the real computeAnalyticsTopology output proves the target is present before blacklisting and absent from all five parts after. It covers lower-, upper- and mixed-case and padded entries, multiple blacklisted nodes, and both sides of a pair.
  • Tests cover an empty or missing blacklist (response unchanged), a missing single part, unknown shapes (fail closed), no mutation of the cached object or recomputer snapshot, cold and warm requests, and concurrent requests, blacklist changes and cache invalidation under -race.
  • qa/scripts/blacklist-test.sh passes against a build with the fix, and reports hide-failed against one without it, for a test node that is present in the topology.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions