Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper output encoding in DevTools
DescriptionImproper output encoding in DevTools
ComponentDevTools
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker553116160
Fix commitb8141806c6b0 (deps/inspector_protocol) +11/-59
CWECWE-116
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-08-26 (per vendor advisory)
Fix landed2026-09-14 (public git)
Disclosed2026-09-22
Patch gap8 days — fix public in source before the advisory

Background

`crdtp`
Chrome’s DevTools-protocol support library, which includes a hand-written JSON parser in crdtp/json.cc used to read CDP messages rather than deferring to V8’s JSON.parse().
JSON comments
non-standard // line and /* ... */ block sequences that RFC 8259 JSON forbids and that JSON.parse() rejects.
Parser differential
a logic-bug class in which two parsers consuming the same bytes disagree on the result, so a message can mean one thing to one consumer and something else to another.
`SkipComment` / `SkipWhitespaceAndComments`
the removed helpers that let the CDP parser silently consume comment runs while scanning for the next token.

Root Cause Analysis

Before the patch, the crdtp JSON parser accepted C-style comments: SkipComment recognized // and /* ... */ runs, and SkipWhitespaceAndComments called it before every token in ParseToken and again after a parsed value in the second hunk’s value-scanning routine. This meant the parser accepted documents that are not valid JSON, diverging from the standard, from JSON.parse(), and from what crdtp itself ever emits, violating the invariant that the CDP parser and standard JSON consumers agree on the same bytes. Such leniency is a parser-differential source: comment bytes are stripped only on the crdtp side, so the same message can decode to different structures across parsing layers.

The fix removes SkipComment entirely and replaces SkipWhitespaceAndComments with SkipWhitespace, which only advances over true whitespace (IsSpaceOrNewLine); a / is no longer swallowed, so it now terminates whitespace-skipping and is parsed as an ordinary (invalid) token. This realigns the parser with RFC 8259 and JSON.parse(), closing the divergence.

Key insight
The single mistake was that the crdtp JSON parser deviated from the JSON standard by silently accepting comments no compliant consumer accepts; the fix deletes all comment handling so the parser matches JSON.parse() and the differential disappears.

Attack Path

  1. Craft a CDP message with comments An attacker who can feed JSON into the crdtp parser embeds // or /* ... */ sequences that the lenient parser tolerates but standard JSON parsers reject.
  2. Create a parse divergence Because comment bytes are consumed only by crdtp’s SkipWhitespaceAndComments, the message decodes to one structure via crdtp and a different one (or a parse error) via a standards-compliant parser.
  3. Trigger improper output encoding (inferred) A DevTools component that later re-encodes or re-parses the message with a standard parser sees the mismatched structure, producing the “improper output encoding” the advisory names.

Impact Assessment

An attacker who can supply CDP JSON gains the ability to have DevTools accept non-standard input whose interpretation diverges from standard JSON consumers, i.e. an improper-output-encoding / logic discrepancy within the DevTools context; the precondition is simply reaching the crdtp JSON parsing path. The patch demonstrates only the removal of comment leniency and no memory-safety primitive, so any escalation beyond the encoding/logic divergence is (inferred). The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3.

Changed Functions

FunctionChangeNotes
if
crdtp/json.cc
modified
for
crdtp/json.cc
modified
while
crdtp/json.cc
modified

Files Changed

  • crdtp/json.cc

Audit Directions

  • Non-standard parser leniency
    audit other hand-written parsers (JSON, CBOR, URL, header, cookie) for accepting constructs the corresponding canonical parser rejects, especially comments, trailing commas, and duplicate keys.
  • Parser differentials
    find places where the same bytes are decoded by two parsers (crdtp vs V8 JSON.parse, serializer vs deserializer) and confirm the two agree, including on error inputs.
  • Serialize/parse round-trip asymmetry
    flag parsers that accept inputs their own serializer never emits, since that emit/accept mismatch is exactly where smuggling and encoding bugs hide.
From b8141806c6b0382b0b741d5c23d45c85168644e6 Mon Sep 17 00:00:00 2001
From: Andrey Kosyakov <caseq@chromium.org>
Date: Mon, 14 Sep 2026 12:05:38 -0700
Subject: [PATCH] Do not accept comments in JSON

Standard does not allow them, `JSON.parse()` does not accept them, we do not emit them, we do not even test them.

Bug: 553116160
Change-Id: Ide07cadae113160a27687b2b4b130baec3ce839d
---
diff --git a/crdtp/json.cc b/crdtp/json.cc
index bc3b8a6..c7a9694 100644
--- a/crdtp/json.cc
+++ b/crdtp/json.cc
@@ -606,74 +606,26 @@
     return false;
   }
 
-  static bool SkipComment(const Char* start,
-                          const Char* end,
-                          const Char** comment_end) {
-    if (start == end)
-      return false;
-
-    if (*start != '/' || start + 1 >= end)
-      return false;
-    ++start;
-
-    if (*start == '/') {
-      // Single line comment, read to newline.
-      for (++start; start < end; ++start) {
-        if (*start == '\n' || *start == '\r') {
-          *comment_end = start + 1;
-          return true;
-        }
-      }
-      *comment_end = end;
-      // Comment reaches end-of-input, which is fine.
-      return true;
-    }
-
-    if (*start == '*') {
-      Char previous = '\0';
-      // Block comment, read until end marker.
-      for (++start; start < end; previous = *start++) {
-        if (previous == '*' && *start == '/') {
-          *comment_end = start + 1;
-          return true;
-        }
-      }
-      // Block comment must close before end-of-input.
-      return false;
-    }
-
-    return false;
-  }
-
   static bool IsSpaceOrNewLine(Char c) {
     // \v = vertial tab; \f = form feed page break.
     return c == ' ' || c == '\n' || c == '\v' || c == '\f' || c == '\r' ||
            c == '\t';
   }
 
-  static void SkipWhitespaceAndComments(const Char* start,
-                                        const Char* end,
-                                        const Char** whitespace_end) {
-    while (start < end) {
-      if (IsSpaceOrNewLine(*start)) {
-        ++start;
-      } else if (*start == '/') {
-        const Char* comment_end = nullptr;
-        if (!SkipComment(start, end, &comment_end))
-          break;
-        start = comment_end;
-      } else {
-        break;
-      }
+  static void SkipWhitespace(const Char* start,
+                             const Char* end,
+                             const Char** whitespace_end) {
+    while (start < end && IsSpaceOrNewLine(*start)) {
+      ++start;
     }
     *whitespace_end = start;
   }
 
-  static Token ParseToken(const Char* start,
-                          const Char* end,
-                          const Char** tokenStart,
-                          const Char** token_end) {
-    SkipWhitespaceAndComments(start, end, tokenStart);
+  Token ParseToken(const Char* start,
+                   const Char* end,
+                   const Char** tokenStart,
+                   const Char** token_end) {
+    SkipWhitespace(start, end, tokenStart);
     start = *tokenStart;
 
     if (start == end)
@@ -999,7 +951,7 @@
         return;
     }
 
-    SkipWhitespaceAndComments(token_end, end, value_token_end);
+    SkipWhitespace(token_end, end, value_token_end);
   }
 
   void HandleError(Error error, const Char* pos) {
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.