{
 "number": 36170,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36170",
 "title": "net: require a dedicated bind for automatic Tor",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2026-09-04T21:25:39Z",
 "updated_at": "2026-09-16T10:07:06Z",
 "age_days": 12,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
 "head_ref": "l0rinc/identify-tor-shared-binds",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [],
 "additions": 38,
 "deletions": 11,
 "changed_files": 6,
 "commit_count": 4,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/36170#pullrequestreview-5123605952"
     }
    ],
    "approach_ack": [
     {
      "login": "winterrdog",
      "url": "https://github.com/bitcoin/bitcoin/pull/36170#pullrequestreview-5138504878"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36257,
     "title": "qa: assert_equals -> assert_true/assert_false",
     "author": "hodlinator"
    },
    {
     "number": 34486,
     "title": "net: Reduce local network activity when networkactive=0",
     "author": "willcl-ark"
    }
   ]
  }
 },
 "acks_parsed": {
  "jeanpablojp": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-06T00:55:26Z",
   "stale": false
  },
  "winterrdog": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-09-08T07:12:25Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "jeanpablojp",
   "winterrdog"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-04T21:23:32Z",
  "last_reviewer_activity": "2026-09-08T07:12:25Z",
  "last_reviewer": "winterrdog",
  "author_silent_days": 12,
  "waiting_on_author_days": 9,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [
   34892
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34892,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "net: Warn when Tor onion service lacks a dedicated onion bind"
   }
  ],
  "conflicts": [
   36257,
   34486
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "doc/release-notes-36170.md",
  "test/functional/feature_bind_extra.py"
 ],
 "body": "**Problem:** When the automatic onion service has no dedicated `-bind=<addr>=onion`, Tor forwards incoming connections to the first normal P2P bind. The node cannot distinguish these connections from direct connections, so network classification is incorrect and Tor peers may inherit permissions granted to the Tor daemon's IP address.\n\n**Fix:** Refuse startup when `-listenonion` is enabled and an explicit `-bind` configuration lacks a dedicated onion bind, following [the recommendation in #34892](https://github.com/bitcoin/bitcoin/pull/34892#issuecomment-5541662236). Users should add `-bind=<addr>=onion` or set `-listenonion=0`.\n\nBecause `-listenonion` is enabled by default when listening, existing configurations with only a normal `-bind` must also be updated. Nodes without explicit `-bind` options retain the default dedicated onion listener.",
 "commits": [
  {
   "sha": "f004c68d8019fb3545437d1fa678143b685b6219",
   "date": "2026-09-04T20:32:25Z",
   "message": "test: give Tor tests dedicated onion binds\n\nSeveral functional tests exercise Tor behavior without relying on a normal bind shared with the automatic onion service.\n\nConfigure dedicated onion binds for those scenarios. This prepares the tests for rejecting shared Tor binds.\n\nCo-authored-by: HouseOfHufflepuff <ahrens@gmail.com>"
  },
  {
   "sha": "0d468fb5d1dd6e223191de16a27e9a9b35da142c",
   "date": "2026-09-04T20:44:20Z",
   "message": "test: characterize shared Tor bind startup\n\nRecord that the node currently starts when the automatic onion service shares a normal bind, alongside successful startup with a dedicated onion bind.\n\nCo-authored-by: HouseOfHufflepuff <ahrens@gmail.com>"
  },
  {
   "sha": "7cac786d3a3cad575c9b7b40b5348126fb49ae79",
   "date": "2026-09-04T21:18:27Z",
   "message": "init: reject shared Tor binds\n\nWhen `-bind` is specified without a dedicated `-bind=<addr>=onion`, the automatic onion service routes incoming connections to the normal bind address.\nWe cannot then distinguish Tor-forwarded connections from direct connections, which makes network classification and address-based permissions unreliable.\n\nRefuse to start and direct operators to configure a dedicated onion bind or disable `-listenonion`.\n\nCo-authored-by: Vasil Dimov <vd@FreeBSD.org>"
  },
  {
   "sha": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
   "date": "2026-09-04T21:23:32Z",
   "message": "doc: note dedicated Tor bind requirement"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-06T00:55:26Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
   "text": "Concept ACK\n\n`0.0.0.0` is the default for `-bind` itself, and `-bind=0.0.0.0:8334=onion` passes the new check while tagging nothing. `addr_bind` comes from `GetBindAddress` on the accepted socket, so what `CreateNodeFromAcceptedSocket` compares against `m_onion_binds` is the concrete address the peer reached.\n\nSame node, same arriving address, same `-whitelist`. With `-bind=0.0.0.0:P=onion` the inbound peer is classified by its IP network in `getpeerinfo` and keeps its `noban`. Spelling that same address out instead of the wildcard gives `onion` with no permissions. `[::]` is classified the same way, and either wildcard is what reaches Tor as the `ADD_ONION` target.\n\nRejecting a wildcard onion bind would close that. Naming `127.0.0.1:8334=onion` in the message would only make it less likely. Is the first one in scope here?"
  },
  {
   "t": "2026-09-06T00:55:27Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "doc/release-notes-36170.md",
   "commit": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
   "in_reply_to": null,
   "text": "The release note covers upgraders. For someone setting Tor up from scratch, the `-bind` help advertises `default: 127.0.0.1:8334=onion`, which still holds with no explicit `-bind` but not once one is given, and section 2 of `doc/tor.md` does not mention the dedicated bind. Would a line in one of the two be worth it?"
  },
  {
   "t": "2026-09-06T00:55:27Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/feature_bind_extra.py",
   "commit": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
   "in_reply_to": null,
   "text": "nit: this could use the helper the file already uses further down, and then `re`, `assert_raises` and `FailedToStartError` drop out of the imports. It does loosen the exit status from exactly 1 to any non-zero. Ran it that way and it passes.\n\n```suggestion\n        self.nodes[2].assert_start_raises_init_error(\n                    self.expected[2][0] + [\"-listenonion=1\"],\n                    \"Error: The Tor onion service cannot share a -bind address\",\n                    match=ErrorMatch.PARTIAL_REGEX)\n```"
  },
  {
   "t": "2026-09-08T07:12:25Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
   "text": "Approach ACK"
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-04T21:25:42Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 3682,
 "text_tokens_estimate": 920,
 "changed_paths": [
  "doc/release-notes-36170.md",
  "src/init.cpp",
  "test/functional/feature_bind_extra.py",
  "test/functional/feature_proxy.py",
  "test/functional/feature_torcontrol.py",
  "test/functional/p2p_private_broadcast.py"
 ],
 "files": [
  {
   "path": "doc/release-notes-36170.md",
   "add": 9,
   "del": 0
  },
  {
   "path": "src/init.cpp",
   "add": 10,
   "del": 8
  },
  {
   "path": "test/functional/feature_bind_extra.py",
   "add": 11,
   "del": 1
  },
  {
   "path": "test/functional/feature_proxy.py",
   "add": 5,
   "del": 2
  },
  {
   "path": "test/functional/feature_torcontrol.py",
   "add": 2,
   "del": 0
  },
  {
   "path": "test/functional/p2p_private_broadcast.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 22,
 "git": {
  "head": "7fb68e39271a6b5d4c306e7532ef22beb9ea669a",
  "head_matches_backup": true,
  "base": "4519933391dd23dbf1a4eceec6dd53d2e9e71cc3",
  "commits": [
   {
    "sha": "f004c68d80",
    "subject": "test: give Tor tests dedicated onion binds",
    "files": 3,
    "add": 8,
    "del": 2
   },
   {
    "sha": "0d468fb5d1",
    "subject": "test: characterize shared Tor bind startup",
    "files": 1,
    "add": 12,
    "del": 1
   },
   {
    "sha": "7cac786d3a",
    "subject": "init: reject shared Tor binds",
    "files": 2,
    "add": 14,
    "del": 13
   },
   {
    "sha": "7fb68e3927",
    "subject": "doc: note dedicated Tor bind requirement",
    "files": 1,
    "add": 9,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "916f5fa6bc840d39",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}