fix(openapi): give every 401-declaring operation the credential it actually wants (#9707) - #9758
Conversation
…tually wants (#9707) applySecurityMetadata only fills a stanza in when requiresApiToken(path) is true, and that returns false for /v1/internal/* and for the anchor-attempt ingest -- not because either is open, but because each carries its OWN credential check. Those operations therefore ended up with the security key entirely ABSENT while still publishing a 401. `[]` and undefined are not the same claim: `[]` is OpenAPI's explicit "this operation needs no credential", undefined is "not stated". A generated client for the provider-credential rotation surface had no credential to send, and a reader could not tell those routes apart from a genuinely public one. The five named operations move to registerRouteSpec entries that declare their auth, so the stanza derives from the declaration rather than from a second path-prefix model of the same policy. The four internal ones emit LoopOverBearer; the anchor-attempt ingest emits OrbBearer, since LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN is an ingest bearer and not a LoopOver API token. Every response code survives the move, including the 503 and the 413/422 pair, and the provider segment keeps its claude-code | codex enum -- registerRouteSpec now accepts a declared `params` that overrides the derived string one, for the rare closed-set segment. The new assertion is that NO operation declares a 401 with an absent stanza, which turned up three more families the two existing parity tests are blind to (both filter on security being present, so an absent one satisfies neither). The GitHub webhook is genuinely gated and now says so; the device-flow entry points are how a caller OBTAINS a credential, so they declare `public` -- their 401 means "that code is not authorized", not "you forgot a token"; and three internal job routes were left silent only because they had no /run sibling to be tabled with. #9706 still owns that family's duplicate registrations and misreported statuses. Fix-what-you-find: OrbWebhookSignature published `x-loopover-signature`, a string that appears nowhere else in src/. Both webhook handlers read `x-hub-signature-256`, so a client generated from this document signed the right body and sent it under a header the server never looks at, earning a 401 it could not diagnose.
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-29 07:23:26 UTC
Review summary Nits — 6 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | 1e19944 | Commit Preview URL Branch Preview URL |
Jul 29 2026, 07:00 AM |
Bundle ReportChanges will increase total bundle size by 731 bytes (0.01%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9758 +/- ##
==========================================
- Coverage 90.27% 89.45% -0.82%
==========================================
Files 904 904
Lines 113213 113210 -3
Branches 26859 26859
==========================================
- Hits 102202 101275 -927
- Misses 9680 10846 +1166
+ Partials 1331 1089 -242
Flags with carried forward coverage won't be shown. Click here to find out more.
|


Closes #9707.
The defect
applySecurityMetadataonly fills a stanza in whenrequiresApiToken(path)is true. That returns false for/v1/internal/*and for the anchor-attempt ingest — not because either is open, but because each carries its own credential check. So those operations ended up with thesecuritykey entirely absent while still publishing a401.[]andundefinedare not the same claim.[]is OpenAPI's explicit "this operation needs no credential";undefinedis "not stated". An operation that declares a 401 while saying nothing leaves a generated client with no credential to send, and a reader unable to tell it apart from a genuinely public route.The existing parity tests are structurally blind to it: both filter on
operation.securitybeing present, so an absent stanza satisfies neither.The fix
The five named operations move to
registerRouteSpecentries that declare theirauth, so the stanza derives from the declaration rather than from a second path-prefix model of the same policy. The four/v1/internal/*ones emitLoopOverBearer; the anchor-attempt ingest emitsOrbBearer, sinceLOOPOVER_LEDGER_ANCHOR_REPORT_TOKENis an ingest bearer and not a LoopOver API token.Every response code survives the move, including the
503and the413/422pair. The provider segment keeps itsclaude-code | codexenum —registerRouteSpecnow accepts a declaredparamsthat overrides the derivedz.string()one, for the rare closed-set segment.The assertion pulled in three more families
The new test asserts that no operation declares a 401 with an absent stanza. That turned up eight offenders, not five, and each needed a different answer:
POST /v1/github/webhookpublic, which emits[]POST /v1/auth/logoutpublictoo; advertising a credential would be a requirement no gate enforces/v1/internal/jobs/*routes/runform to be tabled withThose last three are also #9706's territory. This PR moves only their auth declaration, because the assertion cannot pass while they stay silent; #9706 still owns that family's duplicate registrations and misreported statuses.
Fix-what-you-find
OrbWebhookSignaturepublishedname: "x-loopover-signature"— a string that appears nowhere else insrc/. Both webhook handlers readx-hub-signature-256(src/orb/webhook.ts:25,src/github/webhook.ts:101). A client generated from this document signed the right body and sent it under a header the server never looks at, earning a 401 it could not diagnose.Validation
main:expected [ 'POST /v1/github/webhook', …(12) ] to deeply equal []andexpected 'x-loopover-signature' to be 'x-hub-signature-256'.openapi.jsonconfirms it: 0 operations declare a 401 without a scheme, down from 13.npx vitest run --changed=origin/main: 1923 passed, 143 files.tsc --noEmitclean; the ratchet passes in both directions.