{
 "number": 36374,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36374",
 "title": "torcontrol: escape backslashes in -torpassword",
 "author": "0xShadowX",
 "author_association": "FIRST_TIME_CONTRIBUTOR",
 "created_at": "2026-09-28T17:47:02Z",
 "updated_at": "2026-10-31T20:51:21Z",
 "description_hash": "1b663a122d282b21",
 "age_days": 34,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
 "head_ref": "fix-torpassword-backslash",
 "head_repo": "0xShadowX/bitcoin",
 "head_history": [
  {
   "t": "2026-10-06T20:46:14Z",
   "sha": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe"
  }
 ],
 "additions": 31,
 "deletions": 0,
 "changed_files": 2,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "winterrdog",
      "url": "https://github.com/bitcoin/bitcoin/pull/36374#pullrequestreview-5443278358"
     },
     {
      "login": "jpk68",
      "url": "https://github.com/bitcoin/bitcoin/pull/36374#issuecomment-6321018221"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "winterrdog": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-10-06T19:09:40Z",
   "stale": false
  },
  "jpk68": {
   "kind": "ack",
   "hash": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
   "t": "2026-10-21T16:28:16Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "fjahr",
   "jpk68",
   "winterrdog"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-06T20:46:25Z",
  "last_reviewer_activity": "2026-10-21T16:28:16Z",
  "last_reviewer": "jpk68",
  "author_silent_days": 26,
  "waiting_on_author_days": 11,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [
   35292
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35292,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "test: Add coverage for Tor control `HASHEDPASSWORD` authentication"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/feature_torcontrol.py"
 ],
 "body": "With `HASHEDPASSWORD` authentication, `TorController::protocolinfo_cb` sends the password as a quoted string, `AUTHENTICATE \"<password>\"` (torcontrol.cpp:667). It escapes `\"` but not `\\`.\n\nTor parses that argument as a control-spec QuotedString, where a backslash escapes the next character. So:\n\n- `-torpassword=pa\\ss` reaches Tor as `pass`, authentication fails and no onion service is created\n- `-torpassword=pass\\` escapes the closing quote, so the command is malformed\n\nThis escapes backslashes before quotes.\n\nThe new `test_password_escaping` in `feature_torcontrol.py` uses the mock control server with `HASHEDPASSWORD` and `-torpassword=pa\\ss\"word\\`, and checks the exact `AUTHENTICATE` line. On master it gets `AUTHENTICATE \"pa\\ss\\\"word\\\"`, with the fix `AUTHENTICATE \"pa\\\\ss\\\"word\\\\\"`.\n\nTested against Tor 0.4.9.12 with `HashedControlPassword` set for `pa\\ss\"word\\`: on master bitcoind logs \"tor: Authentication failed\", with this change authentication succeeds and ADD_ONION creates the onion service.\n\nAI tools were used to help find this issue and to prepare the patch and test.",
 "commits": [
  {
   "sha": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
   "date": "2026-10-06T20:46:11Z",
   "message": "torcontrol: escape backslashes in -torpassword\n\nWith HASHEDPASSWORD authentication the password is sent as a quoted\nstring: AUTHENTICATE \"<password>\". Quotes in the password are escaped,\nbut backslashes aren't. Tor reads a backslash in a quoted string as an\nescape for the next character, so a password like pa\\ss reaches Tor as\n\"pass\" and authentication fails, and a password ending in a backslash\nescapes the closing quote.\n\nEscape backslashes before quotes."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-28T19:24:10Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "There is already test coverage suggested for this in #35292, please coordinate with the author there how to proceed.\n\n[quoted text omitted]\nYou should install it and then test it.",
   "id": "c:5876898982",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#issuecomment-5876898982",
   "hash": "9aa6c76322a99313",
   "edited": null
  },
  {
   "t": "2026-09-28T20:16:54Z",
   "kind": "comment",
   "who": "0xShadowX",
   "assoc": "NONE",
   "text": "Installed Tor 0.4.9.12 and tested against it with `HashedControlPassword` for `pa\\ss\"word\\`: on master bitcoind gets \"tor: Authentication failed\", with this change authentication succeeds and ADD_ONION creates the service. I'll ask in #35292 how to combine the test coverage.",
   "id": "c:5877717694",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#issuecomment-5877717694",
   "hash": "c3cb732891fe43dc",
   "edited": "2026-09-28T20:17:25Z"
  },
  {
   "t": "2026-10-06T14:05:57Z",
   "kind": "review_comment",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/feature_torcontrol.py",
   "commit": "898346346bc8a822fa3309d3562528efdc4a9a0d",
   "in_reply_to": null,
   "text": "**nit**: this case only asserts the commands received by the mock, and\n  does not inspect the test node's debug log. `-debug=tor` only\n  enables debug-level logs (_not needed in this case_) and can be dropped\n  here",
   "id": "rc:4196260980",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#discussion_r4196260980",
   "hash": "170a5547c47395c0",
   "edited": "2026-10-06T19:09:40Z"
  },
  {
   "t": "2026-10-06T19:09:40Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "898346346bc8a822fa3309d3562528efdc4a9a0d",
   "text": "approach ACK\n\n---\n\nbackslashes are _not_ escaped on present [`master`](https://github.com/bitcoin/bitcoin/commit/aef8a04966d7ab6c04edc5d19b733e78205405de). to confirm, i ran the test introduced in this PR on `master` and it failed with:\n\n   test output log\n\n```text\n2026-10-03T17:16:33.504732Z TestFramework (ERROR): Unexpected exception:\nTraceback (most recent call last):\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/test/functional/test_framework/test_framework.py\", line 145, in main\n    self.run_test()\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/build/test/functional/feature_torcontrol.py\", line 366, in run_test\n    self.test_hashedpassword_auth()\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/build/test/functional/feature_torcontrol.py\", line 319, in test_hashedpassword_auth\n    assert_equal(mock_tor.received_commands[1], escaped_auth)\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/test/functional/test_framework/util.py\", line 94, in assert_equal\n    raise AssertionError(\"not(%s)\" % \" == \".join(str(arg) for arg in (thing1, thing2) + args))\nAssertionError: not(AUTHENTICATE \"pa\\ss\\\"word\\\" == AUTHENTICATE \"pa\\\\ss\\\"word\\\\\")\n2026-10-03T17:16:33.559119Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.\n```\n\n---\n\nalso, i verified that this PR's motivation is valid and is required by the TorControl spec:\n\n[quoted text omitted]\nfrom https://spec.torproject.org/control-spec/message-format.html#description-format.\n\n---\n\nreply-to: https://github.com/bitcoin/bitcoin/pull/36374#issuecomment-5876898982\n\n[quoted text omitted]\nyes, we had an exchange and reached consensus here: https://github.com/bitcoin/bitcoin/pull/35292#issuecomment-5971847668\n\nsince this PR fixes a bug and adds the fix's regression test, while #35292 is test-only, has a larger diff, and covers more test cases, i think it makes sense for this one to go first. it is smaller and should be easier to review, with fewer conflicts with #35292\n\nneither PR depends on the other, so i can rebase on top of this PR. the remaining conflicts should be straightforward to resolve, and i can handle them afterwards as proposed here: https://github.com/bitcoin/bitcoin/pull/35292#issuecomment-5970759968\n\nthanks for bringing this into my view, @fjahr",
   "id": "r:5429583170",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#pullrequestreview-5429583170",
   "hash": "ce370d6b859567e2",
   "edited": "2026-10-06T19:10:31Z"
  },
  {
   "t": "2026-10-06T20:46:14Z",
   "kind": "force_push",
   "who": "0xShadowX",
   "commit": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
   "id": "push:9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe"
  },
  {
   "t": "2026-10-06T20:46:25Z",
   "kind": "review_comment",
   "who": "0xShadowX",
   "assoc": "NONE",
   "path": "test/functional/feature_torcontrol.py",
   "commit": "898346346bc8a822fa3309d3562528efdc4a9a0d",
   "in_reply_to": 4196260980,
   "text": "Dropped it in 9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe, thanks. The test still fails on master and passes with the fix.",
   "id": "rc:4200274220",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#discussion_r4200274220",
   "hash": "bbe66aa523c87237",
   "edited": null
  },
  {
   "t": "2026-10-07T13:47:58Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
   "text": "tACK 9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe\n\nsuccessfully built and tested with `clang++-19` on FreeBSD `15.0`/`x86_64` and Linux/`x86_64` with `clang++-18`\n\n---\n\ni `git-apply`ied `feature_torcontrol.py`'s patch from this PR onto current `master` at 9dfde64cc3262329051fd05fffe40eecc786a99f, and the test failed with:\n\noutput\n\n```text\n...\n2026-10-07T11:03:15.999507Z TestFramework (INFO): PRNG seed is: 8666784833934009101\n2026-10-07T11:03:16.050000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_wwb8jpcz\n2026-10-07T11:03:16.874367Z TestFramework (INFO): Test Tor control basic functionality\n2026-10-07T11:03:18.980178Z TestFramework (INFO): Test that -torpassword is sent as a correctly escaped quoted string\n2026-10-07T11:03:20.142646Z TestFramework (ERROR): Unexpected exception:\nTraceback (most recent call last):\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/test/functional/test_framework/test_framework.py\", line 146, in main\n    self.run_test()\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/./build/test/functional/feature_torcontrol.py\", line 304, in run_test\n    self.test_password_escaping()\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/./build/test/functional/feature_torcontrol.py\", line 298, in test_password_escaping\n    assert_equal(mock_tor.received_commands[1], 'AUTHENTICATE \"pa\\\\\\\\ss\\\\\"word\\\\\\\\\"')\n  File \"/home/madman/Documents/btc/btc-core/my-btc-fork/test/functional/test_framework/util.py\", line 94, in assert_equal\n    raise AssertionError(\"not(%s)\" % \" == \".join(str(arg) for arg in (thing1, thing2) + args))\nAssertionError: not(AUTHENTICATE \"pa\\ss\\\"word\\\" == AUTHENTICATE \"pa\\\\ss\\\"word\\\\\")\n2026-10-07T11:03:20.193788Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.\n2026-10-07T11:03:20.193916Z TestFramework (WARNING): Not cleaning up dir /tmp/bitcoin_func_test_wwb8jpcz\n2026-10-07T11:03:20.193981Z TestFramework (ERROR): Test failed. Test logging available at /tmp/bitcoin_func_test_wwb8jpcz/test_framework.log\n2026-10-07T11:03:20.194073Z TestFramework (ERROR):\n2026-10-07T11:03:20.194185Z TestFramework (ERROR): Hint: Call /home/madman/Documents/btc/btc-core/my-btc-fork/test/functional/combine_logs.py '/tmp/bitcoin_func_test_wwb8jpcz' to consolidate all logs\n2026-10-07T11:03:20.194235Z TestFramework (ERROR):\n2026-10-07T11:03:20.194285Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.\n2026-10-07T11:03:20.194353Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues\n2026-10-07T11:03:20.194400Z TestFramework (ERROR):\n[node 0] Cleaning up leftover process\n```\n\n---\n\ni also tested on TOR running locally with a `bitcoind` binary built from `master` with password as `pa\\ss\"word\\` and it failed with:\n\ndebug log\n\n```text\n...\n2026-10-07T13:27:26Z torcontrol thread start\n2026-10-07T13:27:26Z [tor] Entering Tor control thread\n2026-10-07T13:27:26Z [tor] Attempting to connect to Tor control port 127.0.0.1:9051\n2026-10-07T13:27:26Z [tor] Successfully connected to Tor control port\n2026-10-07T13:27:26Z Bound to 127.0.0.1:8334\n2026-10-07T13:27:26Z Bound to [::]:8333\n2026-10-07T13:27:26Z Bound to 0.0.0.0:8333\n2026-10-07T13:27:26Z [tor] Connected to Tor version 0.5.0.0-alpha-dev\n2026-10-07T13:27:26Z [tor] Supported authentication method: HASHEDPASSWORD\n...\n...\n2026-10-07T13:27:26Z [tor] Using HASHEDPASSWORD authentication\n2026-10-07T13:27:26Z [warning] tor: Authentication failed\n...\n```\n\n---\n\nthen, i tested with this PR's patch applied and i got these logs:\n\ndebug log\n\n```text\n...\n2026-10-07T13:32:46Z torcontrol thread start\n2026-10-07T13:32:46Z [tor] Entering Tor control thread\n2026-10-07T13:32:46Z [tor] Attempting to connect to Tor control port 127.0.0.1:9051\n2026-10-07T13:32:46Z [tor] Successfully connected to Tor control port\n2026-10-07T13:32:46Z Bound to 127.0.0.1:8334\n2026-10-07T13:32:46Z Bound to [::]:8333\n2026-10-07T13:32:46Z [tor] Connected to Tor version 0.5.0.0-alpha-dev\n2026-10-07T13:32:46Z Bound to 0.0.0.0:8333\n2026-10-07T13:32:46Z [tor] Supported authentication method: HASHEDPASSWORD\n2026-10-07T13:32:46Z [tor] Using HASHEDPASSWORD authentication\n...\n...\n2026-10-07T13:32:46Z [tor] Authentication successful\n...\n```\n\n---\n\nthe TOR daemon version details used (built from source -- `main` at `84caa7e47b1a60c4`):\n```text\nTor version 0.5.0.0-alpha-dev (git-84caa7e47b1a60c4).\nTor is running on Linux with Libevent 2.1.12-stable, OpenSSL 3.0.13, Zlib 1.3, Liblzma 5.4.5, Libzstd 1.5.5 and Glibc 2.39 as libc.\nTor compiled with GCC version 14.2.0\n```\n\nthe command i used to run `bitcoind`:\n```bash\n# used linux's default RAMdisk mount (/dev/shm) just to gain a \"little\" speed.\n# for other systems that do not have it, \"/tmp\" will do just fine\n./build/bin/bitcoind \\\n  -regtest \\\n  -datadir=/dev/shm/bitcoin-tor-test \\\n  -torcontrol=127.0.0.1:9051 \\\n  -torpassword='pa\\ss\"word\\' \\\n  -listenonion=1 \\\n  -printtoconsole=1 \\\n  -debug=tor\n```",
   "id": "r:5443278358",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#pullrequestreview-5443278358",
   "hash": "de9e8a276a5e3fa3",
   "edited": "2026-10-10T19:22:38Z"
  },
  {
   "t": "2026-10-21T16:28:16Z",
   "kind": "comment",
   "who": "jpk68",
   "assoc": "CONTRIBUTOR",
   "text": "ACK 9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
   "id": "c:6321018221",
   "url": "https://github.com/bitcoin/bitcoin/pull/36374#issuecomment-6321018221",
   "hash": "94d8931425282a00",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-09T13:45:08Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-09T16:52:00Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 9777,
 "text_tokens_estimate": 2444,
 "changed_paths": [
  "src/torcontrol.cpp",
  "test/functional/feature_torcontrol.py"
 ],
 "files": [
  {
   "path": "src/torcontrol.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "test/functional/feature_torcontrol.py",
   "add": 29,
   "del": 0
  }
 ],
 "test_lines": 29,
 "git": {
  "head": "9c9bd8cab0f8f5dfebf8d709e0588b42cbdf57fe",
  "head_matches_backup": true,
  "base": "05bc2f53ce0cb239c17dbdd6b261bd2db7d2a940",
  "patch_id": "77243d3f99e04e66",
  "commits": [
   {
    "sha": "9c9bd8cab0",
    "subject": "torcontrol: escape backslashes in -torpassword",
    "files": 2,
    "add": 31,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "77243d3f99e04e66",
 "input_hash": "a24c39937c586e95",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}