Skip to content

fix: let the user delete onion addresses no interface page shows; 0.4.9.12:4 → 0.4.9.12:5 - #34

Merged
MattDHill merged 2 commits into
masterfrom
fix/delete-orphaned-onions
Sep 20, 2026
Merged

MattDHill merged 2 commits into
masterfrom
fix/delete-orphaned-onions

Conversation

@MattDHill

@MattDHill MattDHill commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Summary

Follow-up to #30. A torrc entry the URL plugin cannot export is invisible in the UI and has no delete path, yet it is a live HiddenServiceDir with real keys. Two states get there and stay there:

  • the host is missing while the package is still installed — kept on purpose since Keep an onion whose package is still restoring #32, but permanent if the package changed its host id;
  • the host exists but no longer has a binding on the entry's internal port, so the OS rejects the export with binding-not-found, which exportUrls logs and moves past.

A relay operator who removes every address the interface pages show then keeps the relay and a hidden service warning with nothing left to delete — the support case behind this PR found three such entries on disk against interfaces reading "No addresses".

  • Delete Onion Addresses, a visible action, lists every torrc entry with its .onion hostname and package/host, marks the ones no longer attached to an interface, and deletes the selected entries with their key material. exportUrls and reloadTorrc pick the write up through their torrc watches, so the rows and Tor's config follow.
  • dropOnionService marks an entry undefined and prunes the host and package records it empties, so merge drops them from the file; the per-row delete action uses it too.
  • The reconcile warning names the action; README and instructions cover it. The README also states that the 0.0.0.0:9050 SocksPort binds the container's interfaces only, which the support bot had read as a LAN exposure.

Verified with npm run check; not exercised on a box.

Parking an entry with no reachable port

Second commit. An entry the reconcile pass could not re-derive kept its last target, a bridge port frozen at write time. Freed external ports return to the pool and a binding claims its preferred external port first, so a package installed later that prefers the same port inherited the old onion's traffic, and the address fronted a service it was never attached to.

  • A port with no bridge-reachable address in either mode gets a null target. torrc writes it as a commented-out HiddenServicePort, and comments out the HiddenServiceDir once every port of the entry is, so Tor neither forwards nor publishes the address. Tor rejects a HiddenServiceDir with no ports (verified with tor --verify-config), which is why the block is parked rather than emptied. The annotations stay, the parser reads the commented lines back, and the entry resumes unchanged when the binding returns.
  • A parked port is not exported, and Delete Onion Addresses reads attachment from the target instead of looking the host up.
  • Serializer and parser round-trip verified for a live, a mixed, and a fully parked entry. Not exercised on a box.

down: IMPOSSIBLE: an older parser reads the commented lines as comments, and its first merge writes the parked entries out of torrc, losing the mapping to their key directories. There is no safe target to hand them back, so the downgrade is prohibited.

….9.12:4 → 0.4.9.12:5

A torrc entry the URL plugin cannot export is invisible in the UI and has no
delete path, yet it is a live HiddenServiceDir with real keys. Two states get
there and stay there: the host is missing while the package is still installed
(kept on purpose since the batch-restore fix), and the host exists but no longer
has a binding on the entry's internal port, so the OS rejects the export with
binding-not-found. A relay operator who removes every address the interface
pages show then keeps the relay-and-hidden-service warning with nothing left to
delete.

- A visible Delete Onion Addresses action lists every torrc entry with its
  .onion hostname and owner, marks the ones no longer attached to an interface,
  and deletes the selected entries with their key material.
- dropOnionService marks an entry undefined and prunes the host and package
  records it empties, so merge drops them from the file; the per-row delete
  action uses it too.
- The reconcile warning names the action, and the README states that the
  0.0.0.0:9050 SocksPort is container-internal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@MattDHill
MattDHill force-pushed the fix/delete-orphaned-onions branch from 082ae66 to c309bc7 Compare September 20, 2026 15:55

@helix-a helix-a left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking on one downgrade data-loss path. The current serializer/parser round-trip and Tor config validation both pass, and the package builds cleanly. Separately, please remove the Generated with Claude Code footer from the PR body.

fr_FR: `- Une nouvelle action Supprimer les adresses onion, dans les Actions de Tor, liste chaque adresse .onion hébergée par le serveur, y compris celles qui ne sont plus rattachées à l'interface d'un service, et supprime celles que vous choisissez.
- Une adresse .onion dont l'interface n'a aucun port joignable cesse de répondre jusqu'au retour du port, et conserve sa clé.`,
},
migrations: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking — prohibit the destructive downgrade of parked entries. With down omitted, this graph advertises canMigrateTo >=0.4.9.11:6 && <=0.4.9.12:5, including 0.4.9.12:4. That release's parser recognizes only uncommented HiddenServiceDir / HiddenServicePort lines. After downgrading, its unconditional seedFiles torrc.merge(effects, {}) therefore rewrites a fully parked service out of torrc (and drops parked ports from a mixed service). The key directory remains, but the package/host/port mapping is gone, so upgrading again cannot resume or list the address. Please import IMPOSSIBLE and set down: IMPOSSIBLE, unless there is a safe down migration that can represent parked ports without forwarding them to the stale/reassigned target.

…eaving its target

An entry the reconcile pass could not re-derive kept its last target, a bridge
port frozen at write time. Freed external ports return to the pool and a
binding claims its preferred external port first, so a package installed later
that prefers the same port inherited the old onion's traffic, and the address
fronted a service it was never attached to.

- A port with no bridge-reachable address in either mode gets a null target.
  torrc writes it as a commented-out HiddenServicePort, and comments out the
  HiddenServiceDir once every port of the entry is, so Tor neither forwards nor
  publishes the address; Tor rejects a HiddenServiceDir with no ports, which is
  why the whole block is parked rather than emptied. The annotations stay, the
  parser reads the commented lines back, and the entry resumes unchanged when
  the binding returns.
- A parked port is not exported, and the Delete Onion Addresses action reads
  attachment from the target rather than looking the host up.
- down: IMPOSSIBLE. An older parser reads the commented lines as comments, and
  its first merge writes the parked entries out of torrc, losing the mapping to
  their key directories; there is no safe target to hand them back.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@MattDHill
MattDHill force-pushed the fix/delete-orphaned-onions branch from f9fe676 to 4817daf Compare September 20, 2026 17:01
@MattDHill

Copy link
Copy Markdown
Member Author

Added down: IMPOSSIBLE to the parking commit (amended, force-pushed) and dropped the footer from the body. No safe down migration exists: the only target an older version could write for a parked port is the stale or reassigned one this change removes.

@MattDHill
MattDHill requested a review from helix-a September 20, 2026 17:01

@helix-a helix-a left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the amended head adds down: IMPOSSIBLE, so the graph now reports only =0.4.9.12:5 as a downgrade target; the destructive path is closed. The PR footer is also gone. Re-ran npm run check, npm run build, and Prettier; all pass, and all three architecture CI builds are green.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants