Skip to content

Nuclio, Better Auth, Doorkeeper, and WebOb boundary batch

Operator value

GitHub Advisory Database items published on 2026-06-04 surfaced four reusable offensive-testing patterns worth preserving for authorized assessments:

  • Nuclio Dashboard project write APIs can miss project-membership authorization on destructive write paths.
  • Better Auth device authorization can let any authenticated session approve or deny a pending device code before the intended user claims it.
  • Doorkeeper OpenID Connect dynamic client registration can create public clients that still receive a client_secret, allowing token endpoint authentication with only the public client_id.
  • WebOb redirect normalization can turn control characters plus // into a cross-origin Location header.

Treat these as boundary checks: identity must bind to the target project, device code, OAuth client type, and redirect origin before side effects occur.

Affected surfaces

Nuclio Dashboard project write authorization

  • Advisory: GHSA-m8xg-8xg9-mxhm / CVE-2026-45730
  • Product: Nuclio / github.com/nuclio/nuclio
  • Affected versions: before 0.0.0-20260513101907-1915cd26d514
  • Required position: authenticated low-privilege Nuclio Dashboard user who is not a member of the target project
  • Boundary: GET /api/projects enforces project membership, while PUT /api/projects/{id} and DELETE /api/projects can construct permission options without the member list and bypass OPA filtering

Better Auth device authorization code ownership

  • Advisory: GHSA-cq3f-vc6p-68fh / CVE-2026-45337
  • Product: better-auth
  • Affected versions: >= 1.6.0, < 1.6.11
  • Required configuration: deviceAuthorization() plugin enabled
  • Required position: authenticated attacker who observes a pending user_code before the intended user completes verification
  • Boundary: POST /device/approve and POST /device/deny must be tied to the session that claimed the pending device row

Doorkeeper OpenID Connect dynamic client registration

  • Advisory: GHSA-m6vc-f87m-cc2h / CVE-2026-44476
  • Product: doorkeeper-openid_connect
  • Affected version: 1.9.0
  • Required configuration: Dynamic Client Registration explicitly enabled
  • Required position: ability to register or learn a dynamically registered client_id
  • Boundary: public OAuth clients must not authenticate at the token endpoint as confidential clients using a blank or missing secret

WebOb redirect Location normalization

  • Advisory: GHSA-fh3h-vg37-cc95 / CVE-2026-44889
  • Product: webob
  • Affected versions: <= 1.8.9
  • Required position: ability to influence an application redirect target that is assigned to Response.location
  • Boundary: application-local redirect targets must not normalize into attacker-controlled network-path references such as //attacker.example/path

  • Advisory: GHSA-6hx8-3wjj-gr8g / CVE-2026-54770 (third follow-up, webob < 1.8.11, patched 1.8.11)

  • Product: webob
  • Affected versions: < 1.8.11
  • Required position: same — ability to influence Response.location
  • Boundary: a single leading C0 control byte or ASCII space must not slip past the un-stripped SCHEME_RE / startswith("//") guards and then be silently removed by urllib.parse.urljoin() (Python 3.10+ urlsplit() strips leading/trailing C0 controls and spaces before parsing), turning a guarded value into an open redirect. This is the fix-drift variant of the same Location-normalization gate: the guard runs on the un-stripped value while the joiner normalizes it.

Recon workflow

  1. Confirm that scope permits authenticated application-boundary testing and that destructive operations are either prohibited or explicitly lab-scoped.
  2. Inventory exposed components from safe evidence:
  3. go.mod, Helm charts, images, or admin banners for Nuclio;
  4. package-lock.json, pnpm-lock.yaml, or app auth configuration for Better Auth;
  5. Gemfile.lock and OAuth initializer settings for Doorkeeper OpenID Connect;
  6. Python dependency manifests or WSGI framework stacks for WebOb.
  7. Prioritize targets where the vulnerable feature is reachable:
  8. multi-tenant Nuclio dashboards with low-privilege accounts;
  9. device-flow login UX, CLI pairing, TV/device auth, support workflows, or screen-share-heavy onboarding;
  10. OAuth providers that allow dynamic client registration;
  11. redirect endpoints that accept user-controlled next, return_to, continue, or post-login destinations.
  12. Capture exact version and feature evidence before probing. A dependency match without the relevant feature path is not enough.

Safe validation patterns

Nuclio project write authorization drift

Use a non-production project or a lab clone. Do not test project deletion against live tenant resources unless the program owner has created an explicit disposable target.

  1. Create or request two accounts:
  2. owner@example.test with access to skillz-owned-project;
  3. lowpriv@example.test with any valid Dashboard session but no membership in that project.
  4. Verify the read boundary from the low-privilege session:
GET /api/projects HTTP/1.1
Authorization: Bearer LOW_PRIV_TOKEN

Expected safe result: the target project is absent.

  1. Attempt a harmless metadata-only write against a disposable project, such as changing a canary description or label:
PUT /api/projects/skillz-owned-project HTTP/1.1
Authorization: Bearer LOW_PRIV_TOKEN
Content-Type: application/json

{"metadata":{"labels":{"skillz-authz-canary":"lowpriv-write"}}}
  1. Interpret results:
  2. Contained: write is rejected by project membership policy.
  3. Vulnerable boundary: low-privilege user can modify project metadata or trigger project deletion/write side effects despite read denial.

Better Auth pending device-code hijack

Use a lab tenant and two controlled accounts. Never harvest real user codes from production screens, calls, or logs.

  1. Start a device authorization flow from a test client and record only the lab user_code.
  2. Before the intended account approves it, sign in as a separate attacker-controlled account.
  3. Submit the observed code through the verification/approval endpoint or custom verification UI.
  4. Confirm which account the polling device is bound to:
  5. Contained: approve/deny requires the same session that claimed the pending row, or the first authenticated verification step owns the row.
  6. Vulnerable boundary: the attacker-controlled session can approve or deny another pending user code.

Evidence should show timestamps and account IDs for the pending code owner, approver, and resulting token subject. Redact real tokens.

Doorkeeper dynamic client registration client-type confusion

Validate only against an OAuth lab client or a tenant where the owner has authorized client registration tests.

  1. Confirm Dynamic Client Registration is enabled.
  2. Register a canary client:
POST /oauth/registration HTTP/1.1
Content-Type: application/json

{
  "client_name": "skillz-dcr-canary",
  "redirect_uris": ["https://client.example.test/callback"],
  "scope": "openid"
}
  1. Record whether the response returns both client_id and client_secret while the server stores the application as non-confidential/public.
  2. Attempt token endpoint authentication without a secret for a grant type allowed by the lab configuration:
POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=CLIENT_ID
  1. Interpret results:
  2. Contained: token endpoint rejects missing/blank secret for confidential behavior, or the client is consistently public with no secret-based methods advertised.
  3. Vulnerable boundary: a token is issued with only public client_id knowledge where the provider advertises secret-based client authentication.

WebOb redirect canonicalization bypass

Use an endpoint where redirect targets are intended to remain same-origin. Avoid phishing or real user interaction; capture only server responses.

  1. Find a redirect parameter in scope, for example:
https://app.example.test/login?next=/dashboard
  1. Send canary redirect targets containing ASCII tab, carriage return, or newline before a double slash. Encode controls if the frontend or proxy requires it:
/%09/redirect-canary.example.test/path
/%0d/redirect-canary.example.test/path
/%0a/redirect-canary.example.test/path
  1. Inspect the raw response, not browser behavior alone:
curl -i 'https://app.example.test/login?next=/%09/redirect-canary.example.test/path'
  1. Interpret results:
  2. Contained: Location remains a same-origin path or the input is rejected.
  3. Vulnerable boundary: Location becomes https://redirect-canary.example.test/path or another attacker-controlled origin.

WebOb leading C0 / space redirect bypass (CVE-2026-54770 follow-up)

This extends the canonicalization check to the Python 3.10+ urlsplit() normalization differential: the guards (SCHEME_RE, startswith("//")) run on the raw value, but urljoin() silently strips leading C0 control characters and spaces before parsing.

  1. Confirm the target runs webob < 1.8.11 on Python 3.10+ and has a same-origin redirect gate that assigns user input to Response.location.
  2. Send a leading single control byte or space before a protocol-relative target:
https://app.example.test/login?next=%20//redirect-canary.example.test/path
https://app.example.test/login?next=%00//redirect-canary.example.test/path
  1. Inspect the raw response Location header:
curl -i 'https://app.example.test/login?next=%20//redirect-canary.example.test/path'
  1. Interpret results:
  2. Contained: Location stays same-origin, the input is rejected, or the value is 1.8.11+ (guard and joiner now agree on the normalized value).
  3. Vulnerable boundary: Location becomes https://redirect-canary.example.test/path — the leading byte passed the guard un-stripped and was removed by urljoin().

Evidence to capture

  • Dependency and feature evidence: package version, lockfile line, feature flag, plugin configuration, or owner-provided SBOM.
  • Account and tenant model for authorization tests, using only controlled users and disposable resources.
  • Raw HTTP requests and responses that show the boundary crossing without exposing real credentials or tokens.
  • For OAuth/device-flow checks, token subjects and client IDs should be redacted or canary-only.
  • For redirect checks, include the exact encoded payload and normalized Location header.

Report framing

Frame these as missing binding checks before sensitive side effects:

  • Nuclio: authenticated does not equal authorized for target project write/delete.
  • Better Auth: observed user code does not equal ownership of a pending device authorization row.
  • Doorkeeper: public client_id knowledge must not satisfy confidential client authentication.
  • WebOb: a path-like redirect target must not normalize into an attacker-controlled origin after parser cleanup.

Avoid destructive validation in production. A metadata canary, controlled device code, lab OAuth client, or raw redirect response is enough for a high-quality proof.

Sources

October 12 follow-up: Better Auth device flow approves blind — GET /device omits client_id and scope (GHSA-v9c8-wq66-wf8g/CVE-2026-109346, high, 1.3.8-beta.3–<1.7.0-rc.3)

The deviceAuthorization plugin's approval UI (GET /device) renders no requesting client_id, no requested scope — the user sees a code and a button, nothing about who is asking or what they're asking for. Attack: start a device request at /device/code, social-engineer a signed-in user into approving the displayed code, redeem at /device/token for a live session token. RFC 8628 §5.5's session-spying warning made concrete in a popular library.

Delta over this page's June device-code ownership finding: the June leg was authorization (code vs owner binding); this one is informed-consent rendering — the approval screen itself is the control, and a blank one converts every device flow into a consent-phish vector.

Replayable check for any device-flow implementation (extends the RFC 8628 matrix already cited here): load the approval page with a lab client and check it displays (a) client identity, (b) requested scopes, (c) the code the user is confirming matches, (d) session context being granted. A missing field in that decision table is the finding. Keep proofs to your own client registrations and disposable accounts; never drive real users' approvals.