Add one-forward-pass token scoring and RFC 9207 issuer identification #35

Closed
dcharlot wants to merge 0 commits from score-binary-on-320fa1e into main
Owner

Three changes on this branch.

jouleclaw-runtime: score_binary — one-forward-pass token scoring, the primitive behind cross-encoder reranking (P(yes) vs P(no) on the next token) rather than a full generation per candidate.

Pin the toolchain to Rust 1.97 — matches the pin agent-joule already carries.

jcp-bridge: RFC 9207 issuer identification — the bridge validated the iss claim inside an access token (RFC 9068) but had no check on the authorization response. Those guard different steps: the token claim says who signed a credential already in hand, which only means something once that credential is trusted. RFC 9207 guards the step before it.

In a mix-up attack a client is steered to a malicious authorization server that returns a code or token minted elsewhere. With no issuer on the response the client cannot tell the answer came from a different server than the question, and forwards the credential to the honest one. This is the check MCP 2026-07-28 OAuth 2.1 hardening expects a client to perform.

Two deliberate properties:

  • A missing iss fails closed — a response that cannot name its issuer proves nothing about its origin, which is exactly the condition the check rules out.
  • Decoding is the caller's job and documented as such. The parameter arrives percent-encoded in a redirect query (a did:key issuer's colons), and guessing at decoding here would silently compare an encoded string to a decoded issuer. A test pins that a still-encoded value is refused rather than quietly accepted.

Rebased onto current main; 189 tests pass.

Release Notes:

  • Added RFC 9207 authorization server issuer identification to jcp-bridge
Three changes on this branch. **`jouleclaw-runtime: score_binary`** — one-forward-pass token scoring, the primitive behind cross-encoder reranking (P(yes) vs P(no) on the next token) rather than a full generation per candidate. **Pin the toolchain to Rust 1.97** — matches the pin agent-joule already carries. **`jcp-bridge`: RFC 9207 issuer identification** — the bridge validated the `iss` *claim inside* an access token (RFC 9068) but had no check on the authorization *response*. Those guard different steps: the token claim says who signed a credential already in hand, which only means something once that credential is trusted. RFC 9207 guards the step before it. In a mix-up attack a client is steered to a malicious authorization server that returns a code or token minted elsewhere. With no issuer on the response the client cannot tell the answer came from a different server than the question, and forwards the credential to the honest one. This is the check MCP 2026-07-28 OAuth 2.1 hardening expects a client to perform. Two deliberate properties: - A missing `iss` **fails closed** — a response that cannot name its issuer proves nothing about its origin, which is exactly the condition the check rules out. - Decoding is the caller's job and documented as such. The parameter arrives percent-encoded in a redirect query (a `did:key` issuer's colons), and guessing at decoding here would silently compare an encoded string to a decoded issuer. A test pins that a still-encoded value is refused rather than quietly accepted. Rebased onto current main; 189 tests pass. Release Notes: - Added RFC 9207 authorization server issuer identification to jcp-bridge
Add score_binary / score_binary_tokens: prefill a prompt and return the softmax
probability of one token vs another as the next token — the mechanism of a
cross-encoder reranker (Qwen3-Reranker and friends format query+document into a
yes/no question and read P(yes)). One forward pass, no generation.

- Uses the lfm2-aware cached step builder (run_inplace_step_cached), so it scores
  hybrid conv+attention models too (the plain run_inplace_step assumes every block
  is attention and dies on a conv layer).
- score_binary_tokens takes pre-tokenized input so a reranker prompt's chat
  special tokens (<|im_start|>/<|im_end|>) can be encoded atomically — plain BPE
  would shatter them and the model would score garbage.
- Reads the last-position logits as the final vocab window, robust to whether the
  step returns per-position or last-only logits.

Verified with Qwen3-Reranker-0.6B (real_reranker.rs, ignored): relevant document
0.300 vs irrelevant 0.120. Note: the reranker template must omit the reasoning
<think> block for the 0.6B GGUF.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Match the fleet's current stable (agent-joule moved to 1.97.0 too), so a
standalone jouleclaw build and a build as an agent-joule dependency agree.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The bridge validated the `iss` claim inside an access token (RFC 9068) but had
no check on the authorization *response*. Those guard different steps. The
token claim says who signed a credential already in hand, which only means
something once that credential is trusted; RFC 9207 guards the step before it.

In a mix-up attack a client is steered to a malicious authorization server that
returns a code or token minted elsewhere. With no issuer on the response the
client cannot tell the answer came from a different server than the question,
and forwards the credential to the honest one. Comparing the response's `iss`
against the server the request actually went to is what breaks that, and it is
the check MCP's 2026-07-28 OAuth 2.1 hardening expects a client to perform.

A missing `iss` fails closed rather than being read as benign: a response that
cannot name its issuer proves nothing about its origin, which is precisely the
condition the check exists to rule out.

Decoding is the caller's job and is documented as such. The parameter arrives in
a redirect URI query where a did:key issuer's colons are percent-encoded, and
guessing at decoding here would silently compare an encoded string against a
decoded issuer — a test pins that a still-encoded value is refused rather than
quietly accepted.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
dcharlot closed this pull request 2026-07-27 19:14:31 -04:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!35
No description provided.