{
 "number": 36637,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36637",
 "title": "torcontrol: send -torpassword hex-encoded to avoid escaping bugs",
 "author": "jpk68",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-10-24T02:41:05Z",
 "updated_at": "2026-10-27T10:25:35Z",
 "description_hash": "725b8993971fd6a5",
 "age_days": 8,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
 "head_ref": "tor-escape",
 "head_repo": "jpk68/bitcoin",
 "head_history": [
  {
   "t": "2026-10-27T01:57:50Z",
   "sha": "a1903e3abe515be94e0f345671d9dc67f4c72a6a"
  }
 ],
 "additions": 42,
 "deletions": 6,
 "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/36637#pullrequestreview-5665548329"
     }
    ],
    "stale_ack": [
     {
      "login": "vasild",
      "url": "https://github.com/bitcoin/bitcoin/pull/36637#pullrequestreview-5638420552"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "vasild": {
   "kind": "ack",
   "hash": "87ada856584cec489e6589147c5dca065cc9c5d0",
   "t": "2026-10-24T13:51:24Z",
   "stale": true
  },
  "winterrdog": {
   "kind": "ack",
   "hash": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "t": "2026-10-27T10:25:30Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 1,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "maflcko",
   "vasild",
   "winterrdog"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-27T01:57:50Z",
  "last_reviewer_activity": "2026-10-27T10:25:30Z",
  "last_reviewer": "winterrdog",
  "author_silent_days": 5,
  "waiting_on_author_days": 5,
  "days_since_update": 5
 },
 "refs": {
  "mentioned": [
   36374
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36374,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "torcontrol: escape backslashes in -torpassword"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/torcontrol.cpp",
  "test/functional/feature_torcontrol.py"
 ],
 "body": "The Tor password was sent as a `QuotedString` with only double quotes escaped; as Tor interprets backslash escapes inside it, any password containing a backslash was mangled (or left the string unterminated).\n\nThe Tor Control spec deliberately allows the token to be sent as unquoted hex \"to avoid escaping issues\", as the `SAFECOOKIE` response already is. This patch does the same for the password, and also adds a functional test covering backslashes and quotes.",
 "commits": [
  {
   "sha": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "date": "2026-10-27T01:57:37Z",
   "message": "torcontrol: send -torpassword hex-encoded to avoid escaping bugs"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-24T11:04:44Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "87ada856584cec489e6589147c5dca065cc9c5d0",
   "text": "i think the motivation here is good since hex-encoding removes the need for escaping entirely. concept ACK\n\nalso, the TOR control spec [explicitly allows this](https://spec.torproject.org/control-spec/commands.html?highlight=hashedpa#authenticate:~:text=The%20authentication%20token%20can%20be%20specified%20as%20either%20a%20quoted%20ASCII%20string%2C%20or%20as%20an%20unquoted%20hexadecimal%20encoding%20of%20that%20same%20string%20%28to%20avoid%20escaping%20issues%29%2E):\n[quoted text omitted]\n\n---\n\nre: https://github.com/bitcoin/bitcoin/pull/36637#issue-6008559379\n\n[quoted text omitted]\n#36374 is already underway to fix this specific issue\n\nthe two PRs solve the _same bug in different ways_, so whichever approach reviewers prefer may make the other redundant. this one avoids escaping altogether, while #36374 handles it explicitly. i find this PR's approach more desirable",
   "id": "r:5638016616",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#pullrequestreview-5638016616",
   "hash": "64dfe60ef193a993",
   "edited": "2026-10-26T15:17:15Z"
  },
  {
   "t": "2026-10-24T13:31:07Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "test/functional/feature_torcontrol.py",
   "commit": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "in_reply_to": null,
   "text": "nit:\n```diff\n-    def restart_with_mock(self, mock_tor, extra_args=None):\n+    def restart_with_mock(self, mock_tor, extra_args=[]):\n         mock_tor.start()\n         self.restart_node(0, extra_args=[\n             f\"-torcontrol=127.0.0.1:{mock_tor.port}\",\n             \"-listenonion=1\",\n             \"-debug=tor\",\n-        ] + (extra_args or []))\n+        ] + extra_args)\n```",
   "id": "rc:4367272355",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#discussion_r4367272355",
   "hash": "c94c3132ad278514",
   "edited": "2026-10-24T13:51:25Z"
  },
  {
   "t": "2026-10-24T13:51:24Z",
   "kind": "review",
   "who": "vasild",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "87ada856584cec489e6589147c5dca065cc9c5d0",
   "text": "ACK 87ada856584cec489e6589147c5dca065cc9c5d0\n\nHere is the relevant Tor documentation:\nhttps://spec.torproject.org/control-spec/commands.html?highlight=hashedpa#authenticate\n\nI find it strange that the auth method is called _HASHED_ PASSWORD but the password is sent in clear text \ud83e\udd14\n\nShow Signature\n\n```\n-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nACK 87ada856584cec489e6589147c5dca065cc9c5d0\n\nHere is the relevant Tor documentation:\nhttps://spec.torproject.org/control-spec/commands.html?highlight=hashedpa#authenticate\n\nI find it strange that the auth method is called _HASHED_ PASSWORD but the password is sent in clear text \ud83e\udd14\n-----BEGIN PGP SIGNATURE-----\n\niQRPBAEBCAA5FiEE5k2NRWFNsHVF2czBVN8G9ktVy78FAmrct7QbFIAAAAAABAAO\nbWFudTIsMi41KzEuMTIsMiwzAAoJEFTfBvZLVcu/Dbwf+wT5dRztwyrUVVPaNAXU\nbEvgYtJEdvKWZh7eQVlVKc7HAdxgTmDl/QuGWvEPTNOp0tV84pI2TpI5jjAB7Zcc\nJt9I0BpJ4QbyDgU+ik/kG0kJ5vXnrpKjBDyqhxwKFRsv6zJwmpc2p34T7c7tWUjx\nMLk1ZWgg9NCsIv0pFqx19RHDZtv6FHwVZXn0MQld//fw23sRFE14/1+G3GhJ/ANg\nIYFr0qBC+fuiqvbcVHhrAkUmteav1/3o7JtOJt20VAAB/OE6gFU+ouySQtf1D8Yu\n3xUuvo3RjLmoVdjckXT71h20lhJmZXbXT8W1SrvfpjhRtQsz4eTAhwNLYOyuGEPw\nX3xT2oHLIqqovVJINOna7n7XuVqTFnpTj2MsZ9rRWwONAoeLye2p9ROpayf9XiCF\nZ/ceNmGDx+3xu8BT3R5dqdueEYpwWSeRps+96e51CuT6X3YeOSv0IkstqUg/PAhf\nuqh6Rlw1VzL/Srtd0OQOL8VGZIErXllI77GRdGyC+0SNOGm706S6ReLeehHJzkuN\nBRYklEVrabzbCw8SPNxrOF3ZMkaY9AyVRS5ZfTYz3qWkwzuEA+ySxMXICalh7WiC\ng7nPyIFYNrGHgcSNCZ6nt0RGhEttzSQRqDlx2kPU1BfH3Xj2Ki3J+V55SLOnTQBG\nvsixvOuFAIt3oG1d1YaKrhi/r7Mon/0M5d2rx5QrLRJA+je+zKJUpo3A4/0VhzwS\nv32e++oyAUlM6P6nJkY02GmBUFUxuV43/ZlCSD9GJ3zhwocqTtTe/iL5tagK+8pm\nYUrAxSfu8tkMVG4LyLHm4GFyEIqnzaf4K6C+dXRBEZoNjLXiI+6yMuu1OrnmAJ5k\nUajcibE6PNgP5h2RWMyIwX7KnsyqU9YsrTzDQiuL1s1H0a40x4v2usQvPJdYUk7n\nMLXw0hfmt6Gqyz+ERpu+UT12MWyPdXyvxLehacqBe9G0hjU+a5iW0KTYcfXFJ51q\nE8QJVxXUp2Fwc7ivGxq0xHv252C6IzkajZeUrsdXHzdnQEaxCGDp4HCpYfvl7BfL\nbIOaDdiB0GlAmEKlUIuRSJmmEa8EZaWHuwhB2OIm4fTA0Hxu9Fm6DloQCXB9YrKx\nIf2uNXVaPiwoky+u3CMtBXaMou9sYa5/ycKwDH6DYnNQY/muUK5BFLvdU6Qa45cx\nt72IL+wu3zZkQIE95hEc4SnK7L6GOO5ffX0rHuHpc8JKfnJGohYkXFveBcSwuUfb\nsxBNAI78pV6xWMplNqwv+o8Rvt77U+YW8mF5sihVUKrJGXzsKtHvXLLCjFMYa+Kq\nNEb1rU4uGkQEHYfemz93260rBwiT33Z4kZnKNSeRXkGCWNjqO2CvKER4VIs0YfYX\nIyA=\n=4C4C\n-----END PGP SIGNATURE-----\n```\n\nvasild's public key is [on openpgp.org](https://keys.openpgp.org/vks/v1/by-fingerprint/850E138BA9C0923881DFD55FCE712013582E2230)",
   "id": "r:5638420552",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#pullrequestreview-5638420552",
   "hash": "61a7371592651b28",
   "edited": null
  },
  {
   "t": "2026-10-24T18:29:23Z",
   "kind": "review_comment",
   "who": "jpk68",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/feature_torcontrol.py",
   "commit": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "in_reply_to": 4367272355,
   "text": "Using `extra_args=[]` would cause this to fail `lint_py_lint` in CI, though, yes?",
   "id": "rc:4368067528",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#discussion_r4368067528",
   "hash": "d00b77883404039b",
   "edited": null
  },
  {
   "t": "2026-10-26T09:27:22Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/torcontrol.cpp",
   "commit": "87ada856584cec489e6589147c5dca065cc9c5d0",
   "in_reply_to": null,
   "text": "ralph llm nit\n\nUpdate the password-authentication example (src/torcontrol.cpp:667, head)\n\nThe local summary still documents the password as a quoted token, but this command sends its hex encoding. Update the example or remove it so the authentication summary matches the wire format.",
   "id": "rc:4375707823",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#discussion_r4375707823",
   "hash": "944e1a5e79968335",
   "edited": null
  },
  {
   "t": "2026-10-26T19:34:38Z",
   "kind": "review_comment",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/feature_torcontrol.py",
   "commit": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "in_reply_to": null,
   "text": "the mock could explicitly check the received hex and reply with a `515 Bad authentication` if it does not match, rather than accepting any `AUTHENTICATE`\n\ndiff\n\n```diff\ndiff --git a/test/functional/feature_torcontrol.py b/test/functional/feature_torcontrol.py\nindex 064bc87848..568d3ef685 100755\n--- a/test/functional/feature_torcontrol.py\n+++ b/test/functional/feature_torcontrol.py\n@@ -190,6 +190,10 @@ class TorControlTest(BitcoinTestFramework):\n         self.log.info(\"Test that -torpassword is sent hex-encoded, so special characters survive\")\n\n         class HashedPasswordServer(MockTorControlServer):\n+            def __init__(self, port, expected_password):\n+                self.expected_hex = expected_password.encode().hex()\n+                super().__init__(port)\n+\n             def _get_response(self, command):\n                 if command == \"PROTOCOLINFO 1\":\n                     return (\n@@ -199,20 +203,22 @@ class TorControlTest(BitcoinTestFramework):\n                         \"250 OK\\r\\n\"\n                     )\n                 elif command.startswith(\"AUTHENTICATE \"):\n+                    sent_hex = command.removeprefix(\"AUTHENTICATE \")\n+                    if sent_hex != self.expected_hex:\n+                        return \"515 Bad authentication\\r\\n\"\n                     return \"250 OK\\r\\n\"\n                 return super()._get_response(command)\n\n         password = 'pa\\\\ss\"word\\\\'\n-        mock_tor = HashedPasswordServer(self.next_port())\n+        mock_tor = HashedPasswordServer(self.next_port(), password)\n         self.restart_with_mock(mock_tor, extra_args=[f\"-torpassword={password}\"])\n\n         self.wait_until(lambda: len(mock_tor.received_commands) >= 2, timeout=10)\n-        assert_equal(mock_tor.received_commands[1], f\"AUTHENTICATE {password.encode().hex()}\")\n+        assert_equal(mock_tor.received_commands[1], f\"AUTHENTICATE {mock_tor.expected_hex}\")\n\n         # Authentication succeeded, so the node goes on to create the onion service\n         self.wait_until(lambda: any(c.startswith(\"ADD_ONION \") for c in mock_tor.received_commands), timeout=10)\n\n-        # Clean up\n         mock_tor.stop()\n\n     def test_pow_fallback(self):\n\n```\n\n---\n\nhttps://github.com/bitcoin/bitcoin/pull/36637#discussion_r4375707823 is worth exploring too :)",
   "id": "rc:4381616091",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#discussion_r4381616091",
   "hash": "02b4b6b60832e2b4",
   "edited": "2026-10-26T19:44:12Z"
  },
  {
   "t": "2026-10-27T01:57:50Z",
   "kind": "force_push",
   "who": "jpk68",
   "commit": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "id": "push:a1903e3abe515be94e0f345671d9dc67f4c72a6a"
  },
  {
   "t": "2026-10-27T10:25:30Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
   "text": "tested ACK a1903e3abe515be94e0f345671d9dc67f4c72a6a\n\nsuccessfully built and tested on: Windows `10`/MSVC `19.51.36257`/`x86_64`",
   "id": "r:5665548329",
   "url": "https://github.com/bitcoin/bitcoin/pull/36637#pullrequestreview-5665548329",
   "hash": "3f687078b917704c",
   "edited": null
  }
 ],
 "labels_log": [],
 "state_log": [],
 "text_chars": 6964,
 "text_tokens_estimate": 1741,
 "changed_paths": [
  "src/torcontrol.cpp",
  "test/functional/feature_torcontrol.py"
 ],
 "files": [
  {
   "path": "src/torcontrol.cpp",
   "add": 3,
   "del": 4
  },
  {
   "path": "test/functional/feature_torcontrol.py",
   "add": 39,
   "del": 2
  }
 ],
 "test_lines": 41,
 "git": {
  "head": "a1903e3abe515be94e0f345671d9dc67f4c72a6a",
  "head_matches_backup": true,
  "base": "a773e1e33b349dc819b5a0edc1c7a4839dc73b48",
  "patch_id": "0a57eb9aaf16185f",
  "commits": [
   {
    "sha": "a1903e3abe",
    "subject": "torcontrol: send -torpassword hex-encoded to avoid escaping bugs",
    "files": 2,
    "add": 42,
    "del": 6
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "0a57eb9aaf16185f",
 "input_hash": "b8166674e229aca2",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}