{
 "number": 36200,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36200",
 "title": "rpc: invalid credentials can occupy every HTTP worker",
 "author": "hodlinator",
 "author_association": "MEMBER",
 "created_at": "2026-09-09T08:32:17Z",
 "updated_at": "2026-09-10T11:16:11Z",
 "age_days": 8,
 "draft": true,
 "labels": [
  "RPC/REST/ZMQ"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
 "head_ref": "2026/09/http_auth_sleep",
 "head_repo": "hodlinator/bitcoin",
 "head_history": [],
 "additions": 36,
 "deletions": 6,
 "changed_files": 2,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "willcl-ark"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-10T11:16:11Z",
  "last_reviewer_activity": "2026-09-10T07:36:56Z",
  "last_reviewer": "willcl-ark",
  "author_silent_days": 7,
  "waiting_on_author_days": 0,
  "days_since_update": 7
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/httprpc.cpp"
 ],
 "body": "#### Problem\n\nRPC clients are able to easily DoS the HTTP server due to an obligatory worker thread sleep upon authentication failure.\n\n#### Solution\n\nRemove the sleep.\n\nWhile this allows the guess rate to increase, it also fixes the DoS vector and reduces production code.\n\n#### History\n\nfe98cf8dc5066368b78e9ce208118c3532598dd2 from 2010 introduced RPC authentication along with the sleep. At that point the password authentication was timing-sensitive depending on the length, so the credentials could have been brute-forced. A sleep was added to help mitigate these timing attacks (and also reduce the guess rate).\n\nThe same author then introduced `TimingResistantEqual()` in 42656ea2e552b027e174fdceab7348ffcb8245c4 / https://github.com/bitcoin/bitcoin/pull/2886 in 2013 to solve the timing attack once and for all (https://github.com/bitcoin/bitcoin/issues/2838 / CVE-2013-4165). That made the sleep only serve to reduce the guess rate.\n\n#### Severity\n\nLow since the attack requires RPC clients to be granted network access to a node.",
 "commits": [
  {
   "sha": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
   "date": "2026-09-08T18:48:57Z",
   "message": "rpc: invalid credentials can occupy every HTTP worker\n\nA low-medium service-availability issue lets an admitted HTTP client hold each shared worker for 250 ms per invalid Basic credential, filling the waiting queue and delaying or rejecting authenticated RPC and REST work.\n\nRemove the worker-thread sleep while preserving authentication, warning logs, challenge responses, and timing-resistant comparison."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-09T11:05:42Z",
   "kind": "review_comment",
   "who": "willcl-ark",
   "assoc": "MEMBER",
   "path": "src/httprpc.cpp",
   "commit": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
   "in_reply_to": null,
   "text": "This sleep also slows online password guessing, so I'm not sure I agree that it was made redundant by the addition of `TimingResistantEqual()`.\n\nRemoving it _might_ be reasonable to avoid tying up shared HTTP workers, but I think the description should acknowledge the increased guessing rate.\n\n[quoted text omitted]\nThis attack requires network access to the RPC listener, not valid RPC credentials (if that's what you meant by \"grated access to a node\")."
  },
  {
   "t": "2026-09-09T11:29:44Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/httprpc.cpp",
   "commit": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
   "in_reply_to": 3967646697,
   "text": "Good points!\n\nImproved the PR desc.\n\nIt's not obvious to me that removing this DoS vector is more important than reducing the guess rate you point out. Both of these only occur when we have a malicious attacker who already has network access (on localhost or via `-rpcallowip`).\n\nMoved to draft.\n\nAnother approach that has been mentioned out-of-band is rate-limiting connection attempts from the same IPs. IP source addresses can be spoofed though, so the attacker could make it look as if it was a well-behaving client which failed authentication and induce DoS that way.\n\nMaybe a better approach would be to just expand the existing comment with these trade-offs. What do you think?"
  },
  {
   "t": "2026-09-10T07:36:56Z",
   "kind": "review_comment",
   "who": "willcl-ark",
   "assoc": "MEMBER",
   "path": "src/httprpc.cpp",
   "commit": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
   "in_reply_to": 3967646697,
   "text": "Yeah I think the DoS vector identified here is cheaper/easier to hit (and more annoying), so I'd be in favour of patching that up as you propose. I mainly wanted to point out that I didn't think the sleep was totally superfluous.\n\nPerhaps we can leave repeated guessing up to external tools like fail2ban or similar (and document, if wanted)? i think it could be handled pretty trivially with something like:\n\n```\n# /etc/fail2ban/filter.d/bitcoind-rpc.conf\n# ~ Bitcoin Core debug.log format\n[Definition]\nfailregex = ^\\s*\\[warning\\] ThreadRPCServer incorrect password attempt from <ADDR>:\\d+\\s*$\nignoreregex =\n```\n\nand\n\n```\n# /etc/fail2ban/jail.d/bitcoind-rpc.local\n[bitcoind-rpc]\nenabled = true\nfilter = bitcoind-rpc\nbackend = polling\nlogpath = /var/lib/bitcoind/debug.log\nusedns = no\nport = 8332\nprotocol = tcp\nmaxretry = 5\nfindtime = 10m\nbantime = 1h\nignoreip = 127.0.0.0/8 ::1\naction = nftables[type=multiport, name=bitcoind-rpc, port=\"8332\", protocol=tcp]\n```\n\nThen no action required on our side :)"
  },
  {
   "t": "2026-09-10T11:16:11Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/httprpc.cpp",
   "commit": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
   "in_reply_to": 3967646697,
   "text": "Hm.. wondering if we should add fail2ban docs or not. Maybe it just makes the RPC/HTTP ports seem more like something which we expect to be open to the public internet.\n\nAn alternative approach to removing the 250ms sleep is to spawn a dedicated \"Slow RPC reply\" thread which we could hand off the socket and work to upon authentication failure. That way we don't block any HTTP workers but still keep the guess-rate down. Not sure the added complexity would justify itself though.\n\n*Edit: Addendum - the source IP (range?) we hand off the sleep for should not be allowed to attempt another authentication before the 250ms timeout though... otherwise we don't keep the guess-rate down.*"
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-09T08:32:21Z",
   "action": "labeled",
   "label": "RPC/REST/ZMQ",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-09T11:21:33Z",
   "kind": "convert_to_draft",
   "who": "hodlinator"
  }
 ],
 "text_chars": 4333,
 "text_tokens_estimate": 1083,
 "changed_paths": [
  "src/httprpc.cpp",
  "test/functional/interface_http.py"
 ],
 "files": [
  {
   "path": "src/httprpc.cpp",
   "add": 0,
   "del": 6
  },
  {
   "path": "test/functional/interface_http.py",
   "add": 36,
   "del": 0
  }
 ],
 "test_lines": 36,
 "git": {
  "head": "15ced063da3fc623c89cb0faea40fc4cfc0c3271",
  "head_matches_backup": true,
  "base": "013b0b2de48153b7481c036a353b81a411f1da8f",
  "commits": [
   {
    "sha": "15ced063da",
    "subject": "rpc: invalid credentials can occupy every HTTP worker",
    "files": 2,
    "add": 36,
    "del": 6
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "065df5555bb2c650",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}