Skip to content

feat(confidential): refuse a stream handle in a confidential body - #13

Merged
senamakel merged 1 commit into
mainfrom
confidential-stream-refusal
Aug 14, 2026
Merged

feat(confidential): refuse a stream handle in a confidential body#13
senamakel merged 1 commit into
mainfrom
confidential-stream-refusal

Conversation

@senamakel

Copy link
Copy Markdown
Member

The gap this closes

A confidential call is delivered only to a loaded, hash-verified module
(PR #10). A bulk stream does not travel in that call. The call carries a
StreamRef handle; the bytes travel afterwards as their own Stream.Write
calls, which carry no confidential flag and are routed without an attestation
check.

So this compiled, ran, and looked right — while sending the payload unattested:

let handle = writer.finish().await?;
wallet.call_confidential("StoreKey", (handle,)).await?;  // attests the handle,
                                                         // not the key

The attestation work shipped with that documented as a known limitation. This
turns it from a silent trap into a loud error:

a confidential call cannot carry a stream handle: the stream's bytes travel as
separate unattested writes, so the payload would not be confidential even
though the handle was

Why this is a sender-side rule and not a broker check

The broker cannot enforce this, and must not try. Spotting a handle means
reading the body, and a broker that reads a confidential body is precisely what
the flag exists to prevent — the same invariant behind the BUS_NAME fix in
PR #10.

It is enforced in Connection::call_raw, the one chokepoint every confidential
send passes through, inside the sending peer's own process — which already
owns the body it just built. That covers both Proxy::call_confidential and
hand-built messages. It deliberately is not in Message::validate(), which
the broker calls on ingress and which would inherit the same problem.

The check runs only when confidential is set, so ordinary traffic pays
nothing.

Detection, and the tradeoff it makes

Stream ids are documented as opaque ("Never parse it"), so detection cannot key
off the s{n} prefix a future implementation might not use. It is structural
instead: a detection-only mirror struct with deny_unknown_fields matches an
object whose keys are exactly a StreamRef's with id present, recursing
through arrays and nested objects so a handle buried in a struct is still found.

The cost, stated plainly: a bare {"id": "…"} in a confidential body is
refused even when it was never a stream handle. That is the deliberate direction
to be wrong in — the false positive is loud, local, and fixed by restructuring
the call, whereas the false negative is a secret leaving unattested and nobody
finding out.

What this does not do

It does not make bulk transfer confidential. A secret large enough to want a
stream still has no attested way to travel; that is its own piece of work. This
only removes the silent version of the gap.

Tests

  • Detection across bare, array-nested and struct-nested positions.
  • Negatives: an id alongside other fields, a bare string, a non-string id,
    an empty body, null.
  • End to end: a call_confidential to a genuinely attested recipient is
    refused, while the same handle in a non-confidential call is not intercepted.

Gate

cargo fmt --all -- --check clean; cargo clippy --locked --all-targets --all-features -- -D warnings zero warnings; cargo test --locked --all-features 294 passed; cargo check --locked --no-default-features
compiles. The opt-in real-module tests were also run against built cdylibs
(8 passed).

Note on provenance

This work was written while PR #10 was still open and pushed to its branch
moments after that PR merged, so it was stranded there rather than landing. It
is unchanged apart from being rebased onto current main, where it applies
cleanly.

…connection.rs,crates/tinybus/sr

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@senamakel, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 117 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 76d38bf4-5b3b-4f97-aab4-5b25e4c279f7

📥 Commits

Reviewing files that changed from the base of the PR and between 31a6e82 and 39e1837.

📒 Files selected for processing (6)
  • crates/tinybus/src/broker.rs
  • crates/tinybus/src/connection.rs
  • crates/tinybus/src/stream/mod.rs
  • crates/tinybus/src/stream/stream_test.rs
  • docs/modules/attest/README.md
  • docs/protocol.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@senamakel
senamakel merged commit 76b279d into main Aug 14, 2026
8 of 9 checks passed
@senamakel
senamakel deleted the confidential-stream-refusal branch August 14, 2026 07:31
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