Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
MEDIUM severity

CVE-2026-26309 — envoy

MEDIUM

CVE-2026-26309 is a medium-severity (CVSS 5.3) CWE-193 vulnerability in github.com/envoyproxy/envoy. No vendor fix is recorded yet; mitigation options are listed below.

Envoy has an off-by-one write in JsonEscaper::escapeString()

Also known asBIT-envoy-2026-26309GHSA-56cj-wgg3-x943
Published
Mar 10, 2026
Updated
Aug 12, 2026
Affected
4 pkgs
Patched
None yet
Exploits
None indexed
Exploitation data as of Sep 24, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-26309.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs33th percentile — riskier than 33% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-26309 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 378,567 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

4 pkgs affected
🐹github.com/envoyproxy/envoy🐹github.com/envoyproxy/envoy🐹github.com/envoyproxy/envoy🐹github.com/envoyproxy/envoy

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

An off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string.

Details

The bug is in the control-character escaping path in source/common/common/ json_escape_string.h:67.

  • The function pre-sizes result to the final length: std::string result(input.size() + required_size, '\');
  • For control characters (0x00..0x1f), it emits a JSON escape sequence of length 6: \u00XX.
  • It uses sprintf(&result[position + 1], "u%04x", ...), which writes 5 chars + a trailing NUL (\0) starting at result[position + 1].
  • Then it does position += 6; and writes result[position] = '\'; to overwrite the NUL.
  • If the control character occurs at the end of the output (e.g., the input ends with \x01), then after position += 6, position == result.size(), so result[position] is one past the end (off-by-one), violating std::string bounds/contract.

Concretely, the problematic lines are:

  • source/common/common/json_escape_string.h:69 (sprintf(...))
  • source/common/common/json_escape_string.h:72 (result[position] = '\';)

Potentially reachable from request-driven paths that escape untrusted data, e.g. invalid header reporting:

  • source/common/http/header_utility.cc:538 ~ source/common/http/ header_utility.cc:546 (escapes invalid header key for error text)

Even when this doesn’t immediately crash, it can break the std::string requirement that c_str()[size()] == '\0', which can later trigger UB (e.g., if passed to strlen, printf("%s"), or any C API that expects NUL termination).

//clang++ -std=c++20 -O0 -g -fsanitize=address -fno-omit-frame-pointer
repro_json_escape_asan.cc -o repro_json_escape_asan
ASAN_OPTIONS=abort_on_error=1 ./repro_json_escape_asan
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>

static uint64_t extraSpace(std::string_view input) {
  uint64_t result = 0;
  for (unsigned char c : input) {
    switch (c) {
    case '\"':
    case '\\':
    case '\b':
    case '\f':
    case '\n':
    case '\r':
    case '\t':
      result += 1;
      break;
    default:
      if (c == 0x00 || (c > 0x00 && c <= 0x1f)) {
        result += 5;
      }
      break;
    }
  }
  return result;
}

static std::string escapeString(std::string_view input, uint64_t
required_size) {
  std::string result(input.size() + required_size, '\\');
  uint64_t position = 0;

  for (unsigned char character : input) {
    switch (character) {
    case '\"':
      result[position + 1] = '\"';
      position += 2;
      break;
    case '\\':
      position += 2;
      break;
    case '\b':
      result[position + 1] = 'b';
      position += 2;
      break;
    case '\f':
      result[position + 1] = 'f';
      position += 2;
      break;
    case '\n':
      result[position + 1] = 'n';
      position += 2;
      break;
    case '\r':
      result[position + 1] = 'r';
      position += 2;
      break;
    case '\t':
      result[position + 1] = 't';
      position += 2;
      break;
    default:
      if (character == 0x00 || (character > 0x00 && character <= 0x1f)) {
        std::sprintf(&result[position + 1], "u%04x",
static_cast<int>(character));
        position += 6;
        // Off-by-one when this escape is the last output chunk:
        // position can become result.size(), so result[position] is out of
bounds.
        result[position] = '\\';
      } else {
        result[position++] = static_cast<char>(character);
      }
      break;
    }
  }

  return result;
}

int main() {
  std::string input(4096, 'A');
  input.push_back('\x01'); // ends with a control char -> triggers the buggy
path at the end

  const uint64_t required = extraSpace(input);
  std::string escaped = escapeString(input, required);

  std::printf("escaped.size=%zu\n", escaped.size());
  unsigned char terminator = static_cast<unsigned char>(escaped.c_str()
[escaped.size()]);
  std::printf("escaped.c_str()[escaped.size()] = 0x%02x\n", terminator);

  // If NUL termination is corrupted, this can read past the logical end.
  std::printf("strlen(escaped.c_str()) = %zu\n",
std::strlen(escaped.c_str()));
  return 0;
}```

Affected Packages

4 total
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/envoyproxy/envoyall versionsNo fix
🐹Gogithub.com/envoyproxy/envoy≥ 1.36.0No fix
🐹Gogithub.com/envoyproxy/envoy≥ 1.35.0No fix
🐹Gogithub.com/envoyproxy/envoyall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/envoyproxy/envoy, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Remediation status

    No patched version of github.com/envoyproxy/envoy has shipped for CVE-2026-26309 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-26309 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-26309. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary An off-by-one write in Envoy::JsonEscaper::escapeString() can corrupt std::string null-termination, causing undefined behavior and potentially leading to crashes or out-of-bounds reads when the resulting string is later treated as a C-string. ### Details The bug is in the control-character escaping path in source/common/common/ json_escape_string.h:67. - The function pre-sizes result to the final length: std::string result(input.size() + required_size, '\\'); - For control characters (0x00..0x1f), it emits a JSON escape sequence of length 6: \u00XX.
O3 Security · Impact-Aware SCA

Is CVE-2026-26309 in your dependencies?

O3 Security finds CVE-2026-26309 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-26309: envoy (Medium 5.3) | O3 Security