{
 "number": 36187,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36187",
 "title": "http: Require default IPv4 bind to succeed (again)",
 "author": "hodlinator",
 "author_association": "MEMBER",
 "created_at": "2026-09-07T19:51:07Z",
 "updated_at": "2026-09-11T11:13:45Z",
 "age_days": 9,
 "draft": false,
 "labels": [
  "RPC/REST/ZMQ"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "05de7be9d745df64b04fbedc08abd2fd5159b837",
 "head_ref": "2026/09/req_all_binds",
 "head_repo": "hodlinator/bitcoin",
 "head_history": [
  {
   "t": "2026-09-07T20:26:51Z",
   "sha": "8e30cc95deadb17dde03a51d42d85e93051792f4"
  },
  {
   "t": "2026-09-08T08:35:40Z",
   "sha": "64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2"
  },
  {
   "t": "2026-09-08T20:03:30Z",
   "sha": "bc9985a451bb569e40893490977d3f5087cfb67e"
  },
  {
   "t": "2026-09-08T20:06:45Z",
   "sha": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711"
  },
  {
   "t": "2026-09-11T11:10:54Z",
   "sha": "05de7be9d745df64b04fbedc08abd2fd5159b837"
  }
 ],
 "additions": 128,
 "deletions": 14,
 "changed_files": 3,
 "commit_count": 4,
 "size_bucket": "M",
 "mergeable_state": "unstable",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/36187#issuecomment-5575105539"
     },
     {
      "login": "janb84",
      "url": "https://github.com/bitcoin/bitcoin/pull/36187#pullrequestreview-5139772278"
     },
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/36187#issuecomment-5604417877"
     },
     {
      "login": "winterrdog",
      "url": "https://github.com/bitcoin/bitcoin/pull/36187#pullrequestreview-5172841125"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-07T19:55:37Z",
   "stale": false
  },
  "janb84": {
   "kind": "concept_ack",
   "hash": "64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2",
   "t": "2026-09-08T10:27:34Z",
   "stale": false
  },
  "sedited": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-09T15:28:53Z",
   "stale": false
  },
  "winterrdog": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-10T22:40:36Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 4,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "janb84",
   "l0rinc",
   "sedited",
   "winterrdog"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "unstable",
  "last_author_activity": "2026-09-11T11:13:45Z",
  "last_reviewer_activity": "2026-09-10T22:40:36Z",
  "last_reviewer": "winterrdog",
  "author_silent_days": 6,
  "waiting_on_author_days": 0,
  "days_since_update": 6
 },
 "refs": {
  "mentioned": [
   1822,
   14968,
   26829
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 1822,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2012-09-12",
    "title": "Do not abort if RPC listening for IPv6 fails"
   },
   {
    "number": 14968,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "http: Fail initialization when any bind fails"
   },
   {
    "number": 26829,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2023-01-25",
    "title": "init: Remove unnecessary sensitive flag from rpcbind"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/httpserver.cpp",
  "test/functional/rpc_bind.py"
 ],
 "body": "#### Problem\n\nWhen bitcoind starts and the HTTP IPv4 listen address is occupied by another process, but the IPv6 address is available, the server fails open and generates a cookie file. When bitcoin-cli is run it picks up the cookie file and sends the credentials in plain text to the non-bitcoind process running on the default IPv4 port. An attacker can then use the credentials to commandeer bitcoind over IPv6.\n\n#### Solution\n\n*Safe defaults*:\n* When running with default settings, we now require that the IPv4 bind succeeds.\n* When `-rpcbind` is specified, we now require all HTTP listen socket binds to succeed (fail closed-behavior).\n\nNode runners who only have IPv6 are now required to override `-rpcbind` to only specify the working interface if they were not doing so already.\n\nThe problem of bitcoin-cli sending the credentials to what could possibly be the wrong process remains **in non-default setups**.\n\n#### History\n\nc1d79812f428860e6f624835851d6f3ecd86bbb3 (#1822) from 2012 seems to have introduced this kind of fail open-behavior, making IPv4 continue even though IPv6 was not available (what we want to return to). Later changes made IPv4/IPv6 equivalent, only requiring one.\n\n#14968 from 2018 was an attempt to change the behavior to require all binds to succeed. But it ran into issues with poor IPv6 support on CI. Notable is that towards the end they wanted to still allow fail-open for the default bind addresses, but ran into issues with libevent.\n\n#### Severity\n\nThis credential exfiltration attack requires capability to launch processes on the bitcoind host machine. If we are on the same account we can already read the cookie credentials off disk. So it's only really interesting when an attacker is on a different user account on the same machine.",
 "commits": [
  {
   "sha": "75e77e70736feae820a510af3b355cc184a0e2cb",
   "date": "2026-09-08T19:59:49Z",
   "message": "test: characterize partial HTTP binds\n\nWhen the IPv4 RPC address is already owned, the server starts on its remaining IPv6 listener for both default and explicit bind configurations. bitcoin-cli then sends the generated cookie to the other process on its default IPv4 destination.\n\nRecord the partial startup and exact captured Authorization credential for the following fail-closed fix."
  },
  {
   "sha": "32a3c89b99df1835374186891b7a74966e6fc176",
   "date": "2026-09-08T20:06:36Z",
   "message": "test: Remove attempt to bind RPC to 127.1.1.1\n\nThe next commit requires all binds to succeed, and without this change that one would fail on Mac CI with:\n[InitHTTPServer] [warning] Binding RPC on address 127.1.1.1:19036 failed: Unable to bind to 127.1.1.1:19036 on this computer (bind returned error Can't assign requested address (49))\n\nThe options in this test are there to verify log censorship of sensitive data and bind-addresses were made non-sensitive in #26829 / b9d567454159f062ce84353f5821d6e6daf433bd. That commit added the missing -rpcallowip option to actually make -rpcbind not be ignored any longer. That commit added 127.0.0.1 in addition to the already existing 127.1.1.1, probably because the latter would fail by itself."
  },
  {
   "sha": "4a92148470c14505ff656ea3255d3fceef9f9306",
   "date": "2026-09-11T11:05:49Z",
   "message": "http: fail closed on partial binds\n\nWhen any RPC bind succeeds, initialization ignores every other endpoint failure. A process that already owns the default IPv4 port can therefore receive bitcoin-cli credentials while the node continues on IPv6.\n\nSwitch to safe defaults through only making the default IPv6 address optional, requiring all others. Hosts intentionally providing only an IPv6 interface must use -rpcbind to set that endpoint explicitly.\n\nThis carries the fail-closed policy from bitcoin/bitcoin#14968 now that the in-tree socket implementation reports each bind result.\n\nCo-authored-by: laanwj <126646+laanwj@users.noreply.github.com>\nCo-authored-by: L\u0151rinc <pap.lorinc@gmail.com>"
  },
  {
   "sha": "05de7be9d745df64b04fbedc08abd2fd5159b837",
   "date": "2026-09-11T11:05:49Z",
   "message": "http: Only log *unique* attempts to bind to *resolved addresses*\n\nAlso:\n* Document why it's not worth logging endpoints_seen collisions\n* Make lookup error more explanatory (helps explain why this fails: bitcoind -rpcbind=localhost -rpcallowip=127.0.0.1)"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-07T19:55:37Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Concept ACK, thanks for pushing the fix"
  },
  {
   "t": "2026-09-07T20:07:23Z",
   "kind": "comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "text": "For some reason thought that we weren't enabling the HTTP subsystem by default. Going to re-work the code slightly. Put in draft."
  },
  {
   "t": "2026-09-07T20:26:51Z",
   "kind": "force_push",
   "who": "hodlinator",
   "commit": "8e30cc95deadb17dde03a51d42d85e93051792f4"
  },
  {
   "t": "2026-09-08T08:35:40Z",
   "kind": "force_push",
   "who": "hodlinator",
   "commit": "64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2"
  },
  {
   "t": "2026-09-08T09:18:26Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "test/functional/rpc_bind.py",
   "commit": "64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2",
   "in_reply_to": null,
   "text": "NIT: Leftover assert on started, Line 121 asserts that started is false so started cannot be true at this point.\n\n```suggestion\n        assert 'Unable to start HTTP server' in error\n```"
  },
  {
   "t": "2026-09-08T09:48:41Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "src/httpserver.cpp",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "in_reply_to": null,
   "text": "NIT: I would keep the style of the 2 lines the same (helps the OCD people)\n```suggestion\n        endpoints.push_back({.address = \"127.0.0.1\", .port = http_port});\n```"
  },
  {
   "t": "2026-09-08T10:25:13Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "src/httpserver.cpp",
   "commit": "64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2",
   "in_reply_to": null,
   "text": "non blocking, micro nit: Currently even the required entry/bindings failure only logs as a LogWarning. Is that the correct level for something that aborts startup ?\n\n```suggestion\nif (required) {\n    LogError(\"Binding required RPC endpoint %s failed: %s\", addr->ToStringAddrPort(), result.error());\n    required_failure = true;\n} else {\n    LogWarning(\"Binding optional RPC endpoint %s failed: %s\", addr->ToStringAddrPort(), result.error());\n}\n```"
  },
  {
   "t": "2026-09-08T10:27:34Z",
   "kind": "review",
   "who": "janb84",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2",
   "text": "Concept ACK 64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2\n\nLooks good, some suggestion NITS.\n\nNITl Commit message of c309e5fb73 has a typo / grammar issue \"and without this change that one would Mac CI would fail with:\""
  },
  {
   "t": "2026-09-08T19:19:21Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/httpserver.cpp",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "in_reply_to": 3956567192,
   "text": "Will do if others agree, holding off for now."
  },
  {
   "t": "2026-09-08T20:03:30Z",
   "kind": "force_push",
   "who": "hodlinator",
   "commit": "bc9985a451bb569e40893490977d3f5087cfb67e"
  },
  {
   "t": "2026-09-08T20:06:45Z",
   "kind": "force_push",
   "who": "hodlinator",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711"
  },
  {
   "t": "2026-09-08T20:08:30Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "text": "Latest push addresses all but 1 comment from https://github.com/bitcoin/bitcoin/pull/36187#pullrequestreview-5139772278"
  },
  {
   "t": "2026-09-09T15:28:53Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2026-09-10T22:23:25Z",
   "kind": "review_comment",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "path": "src/httpserver.cpp",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "in_reply_to": null,
   "text": "from the PR description:\n[quoted text omitted]\n\ncan we make the IPv6-only case a bit more actionable in the error message ?\n\nwith the new defaults, an IPv6-only host will fail because the required `127.0.0.1` bind will never succeed, but the user may not know that they need to override `-rpcbind` to use only their IPv6 address. mayhaps the error could point them towards `-rpcbind` rather than just reporting that a required endpoint failed ?\n\ni was thinking of sth like (_feel free to put a spin to it_):\n\n```suggestion\n        LogError(\"Unable to bind all required endpoints for RPC server. If this host does not support IPv4, specify the IPv6 RPC address with -rpcbind.\");\n```"
  },
  {
   "t": "2026-09-10T22:39:15Z",
   "kind": "review_comment",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "path": "src/httpserver.cpp",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "in_reply_to": 3956567192,
   "text": "one of the _\"others\"_ has arrived \ud83d\ude01\n\n---\nanyway, i agree with @janb84\n\nwhen i first looked at the code, i thought there might be a subtle\nreason for using one over the other, but later on i realised they do\nthe same thing. so i think sticking to the same pattern makes sense.\nwhether you use `emplace_back()` or `push_back()` is up to you, as long\nas there is some symmetry"
  },
  {
   "t": "2026-09-10T22:40:36Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "text": "concept ACK\n\nthis makes sense since `bitcoin-cli` defaults to IPv4, so `bitcoind` should have a corresponding IPv4 listener, already working by default as well"
  },
  {
   "t": "2026-09-11T11:10:16Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/httpserver.cpp",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "in_reply_to": 3984022075,
   "text": "Good point. Default behavior for IPv6-only hosts should at least be to get an informative error message."
  },
  {
   "t": "2026-09-11T11:10:54Z",
   "kind": "force_push",
   "who": "hodlinator",
   "commit": "05de7be9d745df64b04fbedc08abd2fd5159b837"
  },
  {
   "t": "2026-09-11T11:13:45Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "690a4b1a6c62aa0c8ce8fdea2f08fa9e4a16b711",
   "text": "Latest push avoids stringly typing `endpoints_seen` and addresses points from https://github.com/bitcoin/bitcoin/pull/36187#pullrequestreview-5172841125."
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-07T19:51:11Z",
   "action": "labeled",
   "label": "RPC/REST/ZMQ",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-07T20:27:21Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-07T21:25:51Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-08T20:08:05Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-08T21:07:45Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-07T19:57:50Z",
   "kind": "convert_to_draft",
   "who": "hodlinator"
  },
  {
   "t": "2026-09-07T20:05:20Z",
   "kind": "renamed",
   "who": "hodlinator",
   "from": "http: Require all binds to succeed",
   "to": "http: Require default IPv4 bind to succeed (again)"
  },
  {
   "t": "2026-09-08T06:49:05Z",
   "kind": "ready_for_review",
   "who": "hodlinator"
  }
 ],
 "text_chars": 6753,
 "text_tokens_estimate": 1688,
 "changed_paths": [
  "src/httpserver.cpp",
  "test/functional/feature_config_args.py",
  "test/functional/rpc_bind.py"
 ],
 "files": [
  {
   "path": "src/httpserver.cpp",
   "add": 57,
   "del": 11
  },
  {
   "path": "test/functional/feature_config_args.py",
   "add": 0,
   "del": 1
  },
  {
   "path": "test/functional/rpc_bind.py",
   "add": 71,
   "del": 2
  }
 ],
 "test_lines": 74,
 "git": {
  "head": "05de7be9d745df64b04fbedc08abd2fd5159b837",
  "head_matches_backup": true,
  "base": "a174dd4017cf97f283b1848bb31998bfa28465e9",
  "commits": [
   {
    "sha": "75e77e7073",
    "subject": "test: characterize partial HTTP binds",
    "files": 1,
    "add": 70,
    "del": 2
   },
   {
    "sha": "32a3c89b99",
    "subject": "test: Remove attempt to bind RPC to 127.1.1.1",
    "files": 1,
    "add": 0,
    "del": 1
   },
   {
    "sha": "4a92148470",
    "subject": "http: fail closed on partial binds",
    "files": 2,
    "add": 54,
    "del": 11
   },
   {
    "sha": "05de7be9d7",
    "subject": "http: Only log *unique* attempts to bind to *resolved addresses*",
    "files": 1,
    "add": 7,
    "del": 3
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "6a9883a71cce9313",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}