Skip to content

Unwrap the raw-body envelope, and read auth_type where the API states it - #17

Merged
garethx merged 2 commits into
mainfrom
fix/raw-body-and-auth-detection
Aug 13, 2026
Merged

Unwrap the raw-body envelope, and read auth_type where the API states it#17
garethx merged 2 commits into
mainfrom
fix/raw-body-and-auth-detection

Conversation

@garethx

@garethx garethx commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

From the OpenClaw session's live-testing notes. I verified both against the same project before acting — three of their other findings didn't apply to us (we don't call /bulk/requests/replay, we already read issues.type, and our boot recovery uses the ledger rather than a request-replay cursor).

hookdeck_get_event_body returned an envelope, not a payload

GET /events/{id}/raw_body answers {"body": "<text>"}. We handed that back whole:

API:       {"body":"{\"marker\":\"queued-…\",\"event\":\"sent.during.downtime\"}"}
our tool:  {"body": "{\"marker\":\"queued-…\",\"event\":\"sent.during.downtime\"}"}

So an agent asking what a provider sent got an escaped string inside a wrapper.

How it hid is the interesting part. I exercised this tool live earlier in the week and it looked correct — the agent's summary showed a clean {"kind":"burst-test","n":1}. The model unwraps it while writing prose. I verified the transcript rather than the tool's return value. A test asserting the tool's own output catches it immediately, and now does.

doctor can sometimes just ask

The check added in #16 infers verification from observed traffic, on the grounds that a source never reveals whether a secret is configured. That holds for a source whose type names the provider, and I generalised it too far. A generic source carrying auth_type: STRIPE reports that auth_type back — measured:

PUT source {type: WEBHOOK, config: {auth_type: STRIPE, auth: {webhook_secret_key: …}}}
  unsigned probe  → verified=false, rejection=VERIFICATION_FAILED   (verification IS on)
  GET source      → config.auth_type = "STRIPE", auth = {}          (readable; secret hidden)

So doctor now reads auth_type when present and falls back to traffic when not. The README no longer claims the secret can only come from the dashboard — true of one shape, not both.

Worth a follow-up, not in this PR

Since the generic shape is API-settable, hermes hookdeck setup could configure provider verification directly instead of leaving it a manual step. That is a provisioning design change — typed sources may carry behaviour we would lose — so it deserves its own decision rather than riding along here.

317 tests, ruff clean. Ships in the wheel.

garethx and others added 2 commits August 13, 2026 11:22
Both from the OpenClaw session's live-testing notes, verified here against the
same project before acting on either.

**`hookdeck_get_event_body` returned an envelope, not a payload.**
`GET /events/{id}/raw_body` answers `{"body": "<text>"}`, and we handed that
back whole — so the model asked what a provider sent and received an escaped
string inside a wrapper. Confirmed live against a real event.

Worth recording how it hid. This tool was exercised live earlier in the week
and looked right, because the agent's summary showed a clean payload: the model
unwraps it while writing prose. What was checked was the transcript, not the
tool's return value. A test asserting the tool's own output would have caught it
on day one, and now does.

**doctor can sometimes just ask.** The earlier check inferred verification from
observed traffic, on the grounds that a source's config never reveals whether a
secret is set. That holds for a source whose `type` names the provider, and I
had generalised it. It is not true of a generic source carrying
`auth_type: STRIPE`, which reports that `auth_type` back — measured: an unsigned
request to one came back VERIFICATION_FAILED, and the source's config showed
`auth_type: STRIPE` with the secret itself hidden.

So doctor now reads `auth_type` when it is there and falls back to traffic when
it is not, and the README no longer says the secret can only come from the
dashboard — that is true of one shape, not both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of my own change, which had introduced a worse bug than the one it
fixed.

Reading `auth_type` told doctor the source was *configured*, and I then
returned early — so a source with `auth_type` set and the wrong secret reported
green while refusing every request. The traffic check I was replacing would
have caught exactly that, and Stripe issues a different signing secret per
endpoint, so a mismatch is the realistic way to get there rather than an exotic
one. Configured now refines the message and never suppresses the check.

Two further corrections while re-reading it:

* The check keyed off `source_type` in our config, which says what we would
  provision rather than what exists. It now reads the source from the API, so a
  source created by hand or changed in the dashboard is seen for what it is —
  and the generic `auth_type` shape, which lives on a WEBHOOK-typed source, is
  no longer excluded by the very filter meant to find it.
* `_payload_text` unwrapped any dict containing `body`. Measured across several
  events, the envelope is always exactly `{"body": "<string>"}`, so it now
  matches that shape and nothing else. A payload is third-party JSON and may
  carry its own `body` field; unwrapping that would hand back a fragment of the
  event as though it were the whole thing.

Four mutations checked. The first attempt at one of them was a no-op — it
assigned a variable the next line overwrote — and reported as survived; re-run
properly, the short-circuit is caught by two tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@garethx
garethx merged commit af3a5f2 into main Aug 13, 2026
8 checks passed
@garethx
garethx deleted the fix/raw-body-and-auth-detection branch August 13, 2026 10:36
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