Skip to content

Endpoint: serve no HTTP/2, and acknowledge bandit CVE-2026-105659 - #981

Merged
mkreyman merged 2 commits into
masterfrom
fix/bandit-h2-advisory
Oct 15, 2026
Merged

mkreyman merged 2 commits into
masterfrom
fix/bandit-h2-advisory

Conversation

@mkreyman

Copy link
Copy Markdown
Owner

mix hex.audit started failing every commit today: CVE-2026-105659 (HIGH, GHSA-69m6-wcv4-rwcf) was published against bandit 1.12.5, with no patched release (introduced at 1.4.0, no fixed event; 1.12.5 is the newest bandit on hex).

The flaw and why it was reachable

Bandit's HTTP/2 stack resets a blocked send's deadline on every WINDOW_UPDATE, so a client trickling window updates pins stream processes indefinitely. Fly's proxy already speaks HTTP/1.1 to the app (fly.toml h2_backend = false), but Bandit still accepted prior-knowledge h2c on port 8080 from anything that reaches it directly: the 6PN, or a self-host with no proxy in front.

Change

  • config/runtime.exs: http_2_options: [enabled: false] on the endpoint's http options. Bandit's initial handler then closes a connection that opens with the HTTP/2 preface instead of switching to its HTTP/2 handler.
  • mix.exs: CVE-2026-105659 joins ignore_advisories with that reasoning and a recheck condition (bandit > 1.12.5), in the same shape as the mint/cowlib/cloak entries.
  • test/loopctl_web/endpoint_http2_test.exs: starts Bandit with the endpoint's own http options and speaks to it over TCP. The h2c preface gets the connection closed unserved, and HTTP/1.1 is still served. With HTTP/2 switched back on, both the config and the preface assertions fail (checked by hand), so the ignore entry cannot outlive the reason it is allowed.
  • CHANGELOG.md: a self-host whose reverse proxy talks h2c to loopctl must switch that upstream to HTTP/1.1.

Default Mark may overrule

HTTP/2 is off at the app, not merely unreached. The alternative was to rely on h2_backend = false alone, which leaves the 6PN and self-hosts exposed.

The advisory database published CVE-2026-105659 (HIGH) against bandit
1.12.5 today, with no patched release (introduced 1.4.0, no fixed event),
and mix hex.audit now fails every commit in the repo.

The flaw is in Bandit's HTTP/2 stack: blocked-send deadlines reset on
every WINDOW_UPDATE, so a trickling client pins stream processes. Fly's
proxy already speaks HTTP/1.1 to the app (h2_backend = false), but Bandit
still accepted prior-knowledge h2c from anything reaching port 8080
directly: the 6PN, or a self-host with no proxy in front.

- config/runtime.exs: http_2_options: [enabled: false]. Bandit then
  closes a connection that opens with the HTTP/2 preface.
- mix.exs: ignore CVE-2026-105659 with that reasoning and a recheck
  condition (bandit > 1.12.5).
- test/loopctl_web/endpoint_http2_test.exs starts Bandit with the
  endpoint's own http options and checks the preface is closed unserved
  while HTTP/1.1 is still served; both behavioural assertions fail with
  HTTP/2 switched back on.
- CHANGELOG: a self-host proxying h2c to loopctl must move to HTTP/1.1.
@mkreyman

Copy link
Copy Markdown
Owner Author

Review round 1 (code) counted at 0da1984386f7 via Skill code-review. The review-round counter's merge gate reads this marker on any machine.

Review round on #981:

- The test now builds listeners through Bandit.PhoenixAdapter.child_specs/2
  over LoopctlWeb.Endpoint.config/1, so it checks what the endpoint boots,
  for every scheme, rather than the raw app env of one.
- The SSL boilerplate in runtime.exs told a self-host to add an https:
  listener with no http_2_options, where ALPN h2 would reach the HTTP/2
  stack again; its example now carries the option and says why.
- A test fails on any bandit version change, so the ignore entry's
  recheck condition has a tripwire.
- Socket legs use a 30s deadline (the sibling websocket test's measured
  reason) and packet: :line, so a short first read cannot fail the
  status-line match. Dropped the dead on_exit; uses ConnCase.
@mkreyman
mkreyman merged commit a1a3c39 into master Oct 15, 2026
16 checks passed
@mkreyman
mkreyman deleted the fix/bandit-h2-advisory branch October 15, 2026 14:39
mkreyman added a commit that referenced this pull request Oct 17, 2026
A new advisory published since #981 — CVE-2026-105660 (MEDIUM, HTTP/1
request-target authority, GHSA-v53v-9hvg-37jf) — failed `mix hex.audit` in the
commit hook for every branch. Bandit 1.13.0 fixed it, and OSV reports 1.13.1
clean of every advisory 1.12.5 carried, including CVE-2026-105659 (HTTP/2),
so the #981 ignore entry comes out too. The endpoint still serves no HTTP/2
(`http_2_options: [enabled: false]`; Fly's proxy speaks HTTP/1.1 anyway) and
endpoint_http2_test.exs keeps that under test, with @acknowledged_bandit moved
to 1.13.1. thousand_island 1.5.0 -> 1.6.0 and hpax 1.0.4 -> 1.1.0 ride along
as bandit's raised requirements.
mkreyman added a commit that referenced this pull request Oct 17, 2026
* CI: pin the scale-test pgvector image to 0.8.7 (#977)

The fleet's host Postgres moved from pgvector 0.8.2 to 0.8.7 under
mkreyman/infra#280; the scale-test job's service image moves in step,
pinned by digest to the 0.8.7-pg16-trixie image already pulled on
beelink (2026-10-13), so the job still never pulls from Docker Hub.
Above the 0.8.4 floor (#965), so the insert/VACUUM race is gone and
test_helper's below-floor warning no longer fires on beelink/nuc runs.

* Deps: bandit 1.12.5 -> 1.13.1; drop the CVE-2026-105659 ignore

A new advisory published since #981 — CVE-2026-105660 (MEDIUM, HTTP/1
request-target authority, GHSA-v53v-9hvg-37jf) — failed `mix hex.audit` in the
commit hook for every branch. Bandit 1.13.0 fixed it, and OSV reports 1.13.1
clean of every advisory 1.12.5 carried, including CVE-2026-105659 (HTTP/2),
so the #981 ignore entry comes out too. The endpoint still serves no HTTP/2
(`http_2_options: [enabled: false]`; Fly's proxy speaks HTTP/1.1 anyway) and
endpoint_http2_test.exs keeps that under test, with @acknowledged_bandit moved
to 1.13.1. thousand_island 1.5.0 -> 1.6.0 and hpax 1.0.4 -> 1.1.0 ride along
as bandit's raised requirements.

* Tests: data_case doc — the host pgvector move is done (#977 review finding)

vacuum_vector_indexes/1's doc still described the infra#280 host-image move as
pending ("until the hosts move"), but the hosts now run 0.8.7 and CI's
scale-test job pins 0.8.7. A below-floor failure now reads as "upgrade the
image", not "wait for infra".
mkreyman added a commit that referenced this pull request Oct 17, 2026
* Deps: bandit 1.12.5 -> 1.13.1; drop the CVE-2026-105659 ignore

A new advisory published since #981 — CVE-2026-105660 (MEDIUM, HTTP/1
request-target authority, GHSA-v53v-9hvg-37jf) — failed `mix hex.audit` in the
commit hook for every branch. Bandit 1.13.0 fixed it, and OSV reports 1.13.1
clean of every advisory 1.12.5 carried, including CVE-2026-105659 (HTTP/2),
so the #981 ignore entry comes out too. The endpoint still serves no HTTP/2
(`http_2_options: [enabled: false]`; Fly's proxy speaks HTTP/1.1 anyway) and
endpoint_http2_test.exs keeps that under test, with @acknowledged_bandit moved
to 1.13.1. thousand_island 1.5.0 -> 1.6.0 and hpax 1.0.4 -> 1.1.0 ride along
as bandit's raised requirements.

* CI: skip self-hosted jobs on fork PRs (#881)

Fork-PR code on the shared self-hosted pool runs as the github-runner uid
every pool slot shares, with read access to other slots' live JIT config and
secrets. The five always-on self-hosted jobs (lint, test, retrieval-eval,
security, dialyzer) and scale-test now skip when the PR head repo is a fork;
a skipped job reports success, so required checks still pass. The human
backstop is the fork-PR approval policy (all_external_contributors), set on
the repo with this change.

Closes #881

* CI: address review round on the fork-PR guard

Four confirmed findings from the lean-review round on PR #988:

1. scale-nightly now carries the fork-PR guard too, keeping the Lint
   comment's "every self-hosted job" true and closing the hole a future
   trigger widening (pull_request as a pre-merge gate) would open.
2. scale-test's header comment now says it also runs on same-repo PRs,
   matching the `if:` below it.
3. config/runtime.exs's http_2 comment stops claiming a fix version for
   CVE-2026-105659 that was never verified; it records what is known
   (bandit 1.13.1 is OSV-clean) and why the option stays.
4. endpoint_http2_test.exs's @acknowledged_bandit comment is reworded to
   the same standard: 1.13.0 fixed CVE-2026-105660, OSV reports 1.13.1
   clean of every advisory 1.12.5 carried.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant