{
 "number": 36536,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36536",
 "title": "log: escape C1 control characters in messages",
 "author": "fametrano",
 "author_association": "FIRST_TIME_CONTRIBUTOR",
 "created_at": "2026-10-15T07:10:08Z",
 "updated_at": "2026-11-01T18:47:47Z",
 "description_hash": "c242d6bc5401ba79",
 "age_days": 17,
 "draft": false,
 "labels": [
  "Utils/log/libs"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "40bb43f158bea7e2efdecff0e17d59d9bf32b924",
 "head_ref": "logging-escape-c1",
 "head_repo": "fametrano/bitcoin",
 "head_history": [
  {
   "t": "2026-10-20T16:46:02Z",
   "sha": "40bb43f158bea7e2efdecff0e17d59d9bf32b924"
  }
 ],
 "additions": 13,
 "deletions": 4,
 "changed_files": 2,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "jpk68",
      "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6329253274"
     },
     {
      "login": "Naomi-Gift",
      "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6334301209"
     },
     {
      "login": "cyb3ralbert",
      "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6336287931"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "jpk68": {
   "kind": "ack",
   "hash": "40bb43f158bea7e2efdecff0e17d59d9bf32b924",
   "t": "2026-10-21T23:57:58Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "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": [
   "Naomi-Gift",
   "cyb3ralbert",
   "jpk68"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-11-01T18:47:47Z",
  "last_reviewer_activity": "2026-10-22T08:26:44Z",
  "last_reviewer": "cyb3ralbert",
  "author_silent_days": 0,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   17095,
   35833
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 17095,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2019-10-16",
    "title": "util: Filter control characters out of log messages"
   },
   {
    "number": 35833,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-09-30",
    "title": "log: prevent user input from injecting fake log lines"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "The log escapes control characters so that a message cannot drive the terminal it is printed on, but it lets through the UTF-8 form of the C1 controls U+0080 to U+009F. VTE, the library behind GNOME Terminal and others, decodes these as CSI, OSC, DCS and the rest, like their `ESC [`, `ESC ]` and `ESC P` forms.\n\nI escape the two-byte sequences 0xC2 0x80 to 0xC2 0x9F as `\\xc2\\x9b` and so on. Since 0xC2 is never a continuation byte, this needs no UTF-8 decoder, and all other non-ASCII text still passes untouched, as asked in #17095 and #35833. Single bytes 0x80 to 0x9F stay as they are, since they occur inside valid UTF-8. The new cases in `test_LogEscapeMessage` fail on master and pass with the change.",
 "commits": [
  {
   "sha": "40bb43f158bea7e2efdecff0e17d59d9bf32b924",
   "date": "2026-10-20T16:27:56Z",
   "message": "log: escape C1 control characters in messages\n\nLogEscapeMessage escapes bytes below 0x20 and 0x7f, but passes the\nUTF-8 encodings of U+0080 to U+009F (0xC2 0x80 to 0xC2 0x9F), which\ninclude CSI, OSC and DCS. VTE-based terminals, such as GNOME Terminal,\nact on them.\n\nEscape these two-byte sequences as well. 0xC2 is never a continuation\nbyte, so no decoder state is needed, and every other UTF-8 sequence\nstill passes through unaltered."
  }
 ],
 "timeline": [
  {
   "t": "2026-10-20T16:46:02Z",
   "kind": "force_push",
   "who": "fametrano",
   "commit": "40bb43f158bea7e2efdecff0e17d59d9bf32b924",
   "id": "push:40bb43f158bea7e2efdecff0e17d59d9bf32b924"
  },
  {
   "t": "2026-10-21T23:57:58Z",
   "kind": "comment",
   "who": "jpk68",
   "assoc": "CONTRIBUTOR",
   "text": "ACK 40bb43f158bea7e2efdecff0e17d59d9bf32b924",
   "id": "c:6329253274",
   "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6329253274",
   "hash": "fe4586f46a6e7bda",
   "edited": null
  },
  {
   "t": "2026-10-22T06:26:51Z",
   "kind": "comment",
   "who": "Naomi-Gift",
   "assoc": "NONE",
   "text": "tACK 40bb43f158bea7e2efdecff0e17d59d9bf32b924\ntest_LogEscapeMessage passes",
   "id": "c:6334301209",
   "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6334301209",
   "hash": "f27cd6592267f71b",
   "edited": null
  },
  {
   "t": "2026-10-22T08:26:44Z",
   "kind": "comment",
   "who": "cyb3ralbert",
   "assoc": "CONTRIBUTOR",
   "text": "tACK 40bb43f158bea7e2efdecff0e17d59d9bf32b924\n\nTested on Linux x86_64 (Ubuntu 24.04), g++ 13.3.0, Release: `logging_tests` pass, also with this branch merged onto current master (de96f3ae). With U+009B and U+009F in a regtest datadir path, debug.log has `\\xc2\\x9b` and `\\xc2\\x9f` on this branch and the raw bytes on the merge-base.\n\nNit: the two new checks don't independently pin down both boundaries. Changing the lower bound to `>= 0x7f` or the end check to `i + 1 <= str.size()` still passes them. The trailing `\\xc2` in the second check is a string literal, so `str[str.size()]` reads the terminating NUL.\n\nAdding after line 141:\n\n```cpp\nBOOST_CHECK_EQUAL(BCLog::LogEscapeMessage(\"\\xc2\\x7f\"), \"\\xc2\" R\"(\\x7f)\");\nBOOST_CHECK_EQUAL(BCLog::LogEscapeMessage(std::string_view{\"\\xc2\\x9b\", 1}), \"\\xc2\");\n```\n\nBoth pass on this branch. Each fails only with its corresponding mutant (lines 142 and 143, respectively; Debian 13 x86_64, GCC 14.2.0, Release).\n\nNit: the `LogEscapeMessage` doc comment and the test comment \"ASCII and UTF-8 must pass through unaltered\" don't mention the C1 exception. Also, U+0100 isn't adjacent to the tested range: that case checks a different lead byte followed by `0x80`. The test itself is fine; only the comment is misleading.",
   "id": "c:6336287931",
   "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6336287931",
   "hash": "c13680c8aa86f330",
   "edited": null
  },
  {
   "t": "2026-11-01T18:47:47Z",
   "kind": "comment",
   "who": "fametrano",
   "assoc": "NONE",
   "text": "@cyb3ralbert thanks, both nits are right: the two extra checks pin each boundary, and the comments should mention the C1 exception. I will apply them in a push if a maintainer prefers, and otherwise leave the head as it is, so the three reviews of 40bb43f158 stay valid.",
   "id": "c:6562301498",
   "url": "https://github.com/bitcoin/bitcoin/pull/36536#issuecomment-6562301498",
   "hash": "5058ca1c2278c1a8",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-15T07:10:12Z",
   "action": "labeled",
   "label": "Utils/log/libs",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-20T08:25:49Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-20T17:49:40Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 2841,
 "text_tokens_estimate": 710,
 "changed_paths": [
  "src/logging.cpp",
  "src/test/logging_tests.cpp"
 ],
 "files": [
  {
   "path": "src/logging.cpp",
   "add": 7,
   "del": 4
  },
  {
   "path": "src/test/logging_tests.cpp",
   "add": 6,
   "del": 0
  }
 ],
 "test_lines": 6,
 "git": {
  "head": "40bb43f158bea7e2efdecff0e17d59d9bf32b924",
  "head_matches_backup": true,
  "base": "4303f3b7f0775cdbbe77faddf142e1e44fa590ca",
  "patch_id": "9c0dc0727e20a5a1",
  "commits": [
   {
    "sha": "40bb43f158",
    "subject": "log: escape C1 control characters in messages",
    "files": 2,
    "add": 13,
    "del": 4
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "9c0dc0727e20a5a1",
 "input_hash": "f17f05c1bf222879",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}