feat(webhook): bridge Orca completions to Hermes - #95408
Open
saedol-lang wants to merge 2 commits into
Open
Conversation
Ports the accepted Orca completion bridge (86a2b138e, written against 470cf66) onto current main. The two histories share no merge-base, so the port was computed as a per-file three-way merge of blobs supplied by hash — no rebase, cherry-pick or history join was used. Adds tools/orca_bridge.py plus the webhook bridge route, a gateway supervisor with start/stop lifecycle, durable async_delegations records for external completions, four additive `hermes webhook orca-*` CLI verbs, a config example block and docs. The POST body is never trusted: run identity is re-queried from Orca, and only a real workerState of `succeeded` publishes. Failure states and unrecognized states fail closed; the no-ledger sentinels (`unsupervised`, `unknown`) defer to the Task ledger. Attacker-supplied goal/session_key/summary never reach the published event. Security decision this port had to make: current main's Linear branch (`linear-signature`, NousResearch#87348) signs the body only with no timestamp binding, which made it a fourth downgrade path around the V2 replay-protected HMAC on an `orca_bridge: true` route — a captured Linear signature would have authenticated a bridge POST. Linear now sits inside the `require_replay_protection` guard alongside GitHub and GitLab, which is the class sweep rather than a fix limited to the schemes the original review enumerated. Ordinary routes are unchanged and NousResearch#87348 stays green. Verified in this checkout: - scripts/run_tests.sh over the four bridge suites -> 214 passed, 0 failed (26 + 16 + 70 + 102), including 4 new Linear downgrade cases. - Mutation control: hoisting Linear back out of the guard in a scratch copy turns tests/gateway/test_webhook_orca_bridge.py red -> 3 failed, 67 passed, and the failure mode is the request reaching the bridge handler, i.e. auth passed. Checkout never mutated. - Current-main webhook/delegation regression set (15 files) -> 132 passed, 1 failed. That failure, test_shutdown_forensics.py::TestSpawnAsyncDiagnostic::test_spawns_subprocess_and_writes_output, reproduces identically on a pristine export of 708b245, so it is pre-existing and not caused by this port. - ruff check on the 10 changed .py files -> All checks passed! - git diff --check clean; no conflict markers in any of the 12 files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCFhX81bc5AujbbdGoKAQN (cherry picked from commit 746f6abc29cf67bb584fbbbb405d05ae98d9f6f3)
…g to 500 PRE-EXISTING UPSTREAM DEFECT — not introduced by the Orca bridge port in the preceding commit. `_hmac_str_equal` is byte-identical on f0c0c98 (current main); that commit only re-indented its call sites when it moved the body-only schemes inside the replay-protection guard. aiohttp decodes header bytes that are not valid UTF-8 using `surrogateescape`, so an attacker-supplied signature header arrives holding lone surrogates (`\udce9` for a latin-1 byte, and similarly for a raw 0xFF). The helper then called `provided.encode()` with the strict codec, which raises `UnicodeEncodeError` out of the request handler and returns HTTP 500. That is the exact failure the helper's own docstring says it exists to prevent — the original fix reached non-ASCII input that is still *valid* UTF-8, one codec short of the malformed case. Reproduced on a real socket against a pristine export of f0c0c98 with the bridge absent entirely, on a 0.0.0.0-bound (network-reachable) route: valid github signature -> 200 X-Hub-Signature-256, latin-1 -> 500 linear-signature, latin-1 -> 500 X-Gitlab-Token, 0xFF -> 500 X-Webhook-Signature, 0xFF -> 500 It fails closed — no request is ever authenticated by this path and the 500 body is aiohttp's generic text, so no internal state reaches the client — but the contract is 401, and every scheme reaches the same helper, so this is the whole class rather than the one header that surfaced it. Re-encoding with the same error handler round-trips the surrogates back to the original wire bytes, keeping the constant-time comparison while letting a hostile header be rejected as bytes. Verified in this checkout: - New `TestMalformedSignatureHeaderBytes` drives REAL wire bytes over a real `adapter.connect()` socket (no client library, which would re-encode them), parametrized over all five schemes x two invalid-UTF-8 byte patterns, plus a positive control -> 11 passed. - Load-bearing: the same test against the unfixed helper is RED, 10 failed / 1 passed — every scheme x pattern combination, control still green. - tests/gateway/test_webhook_adapter.py -> 47 passed (36 pre-existing + 11). - ruff check + py_compile on both changed files -> clean; git diff --check clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EZ1X3rQHTcKdmsyRbDDR1
Author
|
CI is currently waiting on the standard maintainer approval required for workflows from a new fork contributor. All three workflow runs ( |
Contributor
This diff is too large for an automated line-level review (>30 files or >200KB). Recommend a manual review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in, authenticated Hermes-to-Orca completion-webhook bridge so successful Orca runs can wake the matching Hermes delegation exactly once, with durable restart recovery and additive CLI lifecycle controls.
Also includes a narrow pre-existing webhook hardening fix: malformed non-UTF-8 signature-header bytes now fail closed with HTTP 401 instead of escaping the request handler as HTTP 500.
Design / footprint
This follows Hermes' narrow-waist guidance:
hermes webhookCLI rather than adding a core model tool;orca_bridge: trueroute is configured;Security contracts
INSECURE_NO_AUTH.goal,session_key,summary, and other attacker-controlled fields are never published.workerState == succeededpublishes; failure/unknown states fail closed.unsupervised/unknownsentinel states defer to the durable Task ledger.shell=False) with timeout and strict run-id validation.Current-main integration
Final branch is based on exact upstream
main:1fe0f2f3ac9748ce799272eb93bee2937b5ab80285f6b5cd9f728210e53866b8d6c7437b1c9c32864ecc3562cd2cab4e8ba9f21f4c09a01fd12dcdb9The accepted bridge candidate was replayed onto current main without conflicts. Patch IDs, changed blobs, authorship, commit messages, and trailers were verified preserved. The sole intervening upstream commit touched only cron files and had zero overlap with this 13-file diff.
Verification on final exact SHA
Static / integrity
git diff --check origin/main..HEAD— cleanruff checkover all changed Python files — cleanpy_compileover all changed Python files — cleanFocused bridge contract suite
Using the repository-mandated
scripts/run_tests.shwrapper:tests/tools/test_orca_bridge.py— 102 passedtests/gateway/test_webhook_orca_bridge.py— 70 passedtests/gateway/test_orca_bridge_lifecycle.py— 26 passedtests/hermes_cli/test_webhook_orca_cli.py— 16 passedMalformed signature-header regression
TestMalformedSignatureHeaderBytesdrives real invalid UTF-8 bytes over a real socket for all five signature schemes, plus a valid control:This defect was independently reproduced on pristine upstream main with the bridge absent: valid signature returned 200, while malformed GitHub/Linear/GitLab/legacy-V1 signature bytes returned 500. The fix converts the full class to 401 while retaining constant-time byte comparison.
Risk-scaled regressions
Final aggregate gate: 269 passed, 1 failed. The sole failure is:
tests/tools/test_shutdown_forensics.py::TestSpawnAsyncDiagnostic::test_spawns_subprocess_and_writes_outputIt reproduces identically on a pristine export of the exact base (
1fe0f2f3a): 1 failed / 9 passed. On macOS the test invokes a missing GNUtimeoutexecutable and receivesNone; relevant files are byte-identical between base and candidate. It is unrelated to this change.The new upstream cron tests also pass: 17 passed.
Real E2E
WebhookAdapter.connect()loopback listener on an ephemeral TCP socket: 46 auth/replay/route checks passed.orca-register -> orca-runs -> signed POST -> replay -> orca-sweep.Mutation controls
All controls turned the focused suite RED and the checkout was restored byte-for-byte:
Configuration
See the updated webhook guide and
cli-config.yaml.example. The feature is opt-in and requires a bridge route with replay-protected HMAC plus the documented Orca CLI registration/reconcile flow.Known non-blocking note
The CLI currently passes the webhook secret to the local
orca bridge registersubprocess in argv for runtime compatibility. This is documented as a local process-list exposure tradeoff; no secret is written to logs. A stdin/fd transport can be adopted when the Orca CLI supports it.