Skip to content

Gate the A.4 leg on content type, and stop it answering with Mls - #111

Merged
germ-mark merged 2 commits into
mainfrom
llm/a4-leg-content-type-gate
Jul 27, 2026
Merged

Gate the A.4 leg on content type, and stop it answering with Mls#111
germ-mark merged 2 commits into
mainfrom
llm/a4-leg-content-type-gate

Conversation

@germ-mark

Copy link
Copy Markdown
Contributor

Follow-up to #110, which noted this in its "not fixed here" section.

Two problems at process_a4_leg, both about a door that carries application messages only.

A non-application leg was applied before being refused. The routing tag is chosen by the sender, and process_incoming_message validates and applies atomically. So a Commit behind the EK tag was applied — moving the recv-PQ epoch — and only then refused by the match below. The content type rides the PrivateMessage's plaintext framing, so MlsMessage::description() can reject a non-application leg before any keys or state are touched.

Every rejection reported Mls. That carries the fatal disposition: "our own state may be inconsistent, discard the session". A host reading it literally tears the session down and re-establishes — so a frame the peer chose must never be able to ask for one. All of them are DecryptionFailed now, which is where #110 already put the decrypt failure itself.

Test notes

test_non_application_leg_is_refused_with_the_group_untouched pins the refusal's shape: frame-level, nothing staged, no epoch moved. Verified by mutation that it fails under the pre-change behavior (gate removed and map_err(|_| Mls) restored).

Its doc says plainly what it does not pin. The Commit it injects belongs to the classical group, so mls-rs refuses it on group id alone — which makes it a test of the refusal's shape, not of the gate. Pinning the gate itself needs an unapplied commit for the recv-PQ group at exactly its current epoch while the pq_inflight guard is simultaneously open, and those two conditions fight each other through the public API. I'd rather leave the gate as documented defense-in-depth than ship a test that looks like it covers it and doesn't.

cargo test (291 passing), cargo clippy --all-targets --features awslc,benchmark_util -- -D warnings and cargo fmt --check all clean.

🤖 Generated with Claude Code

Two problems at process_a4_leg, both about a door that carries application
messages only.

The routing tag is chosen by the sender, and process_incoming_message validates
and APPLIES atomically. So a Commit behind the EK tag was applied — moving the
recv-PQ epoch — and only then refused by the match below. The content type
rides the PrivateMessage's plaintext framing, so MlsMessage::description() can
reject a non-application leg before any keys or state are touched.

And every rejection here reported Mls, which carries the fatal disposition:
"our own state may be inconsistent, discard the session". A host reading that
literally tears the session down, so a frame the PEER chose must never be able
to ask for one. All of them are DecryptionFailed now.

The new test pins the refusal's shape — frame-level, nothing staged, no epoch
moved — and its doc says plainly what it does not pin: the injected Commit is
from the classical group, so mls-rs refuses it on group id alone. Pinning the
gate itself needs an unapplied commit for the recv-PQ group at its current
epoch while pq_inflight is simultaneously open, and those conditions fight each
other through the public API. Verified by mutation that the test fails under
the pre-change behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 99cabe3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

The gate and the DecryptionFailed rejections carry over onto the generic
process_a4_leg unchanged. Two reconciliations where main met this change:
the smuggled-proposal test now pins the pre-ingest gate refusal (with the
legacy arm's cache-drop as the backstop for paths that do ingest), and the
non-application-leg test's scope note is rewritten for the classical door —
its commit now passes the guard phase but still dies at PSK resolution, so
the recv-classical epoch is asserted directly and the gate remains defense
in depth, verified by mutation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@germ-mark
germ-mark merged commit 2a55697 into main Jul 27, 2026
9 checks passed
@germ-mark
germ-mark deleted the llm/a4-leg-content-type-gate branch July 27, 2026 19:10
germ-mark added a commit that referenced this pull request Jul 27, 2026
Pre-release fixes for 0.15.0: #111 changeset + archive-version rule
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