ORM query-grammar injection and authorization-target fallback: the Mongoid cluster, OpenStack Blazar parameter-name drift, Cotonti clock-derived tokens, and Azkaban sibling-action authz gaps¶
Source: hourly offensive-security scan of GitHub Security Advisories, 2026-09-18/19 (waves published 2026-09-18T18:31Z, 21:32Z, and 2026-09-19T00:32Z).
Two durable themes tie the promoted records:
- The ORM/ODM layer parses your input as query grammar, not as data. A Ruby Mongo ODM shipped query paths that reflect caller-supplied field names into internal method invocation, pass string criteria to server-side JavaScript, and accept operators from untrusted filter bodies — all reachable unauthenticated wherever an app forwards request data into a query builder.
- Authorization code that builds its check target from a parameter name the router never delivers silently falls back to the caller's own scope. A lease API "authorized" every cross-tenant mutation because the policy wrapper looked the object up under a keyword that was always absent.
Advisory table¶
| Product | GHSA | CVE | CVSS | Boundary |
|---|---|---|---|---|
| Mongoid (embedded-doc query path) | GHSA-36hj-r7fq-q38m | CVE-2026-93762 | 9.8 | Unsafe reflection: an externally supplied field name passed to in-memory query methods on embedded documents resolves to internal methods instead of attributes → unintended disclosure of stored document data and permanent record removal, unauthenticated if the app forwards request data |
| Mongoid (persistence layer) | GHSA-8899-fxx7-fwx3 | CVE-2026-93765 | 9.1 | Same reflection class on the write/persistence path: keys passed through from an unauthenticated party trigger unintended internal method invocation instead of the intended array-field update → record removal + app hang |
| Mongoid (query builder) | GHSA-vr6j-ghpr-hmgf | CVE-2026-93759 | 8.6 | A string-typed query criterion is not neutralized — it is forwarded to MongoDB as a server-side JavaScript expression (the $where class): attacker-chosen code evaluated by the database engine → field disclosure, poisoned selection for app-initiated writes |
| Mongoid (filter operators) | GHSA-mm58-qxpf-v4c7 | CVE-2026-93760 | 8.2 | No restriction on which query operators may come from caller-supplied filter data handed to query-building methods → unauthenticated party steers database evaluation (classic $ne/$gt/$regex smuggle, but at library level the "trusted" helper itself is the sink) |
| Mongoid (nested attributes) | GHSA-7x44-w6qh-mj7w | CVE-2026-93758 | 8.1 | IDOR in nested-attributes handling: a basic-privilege user supplies a record identifier that is not their own; the record is looked up without ownership/scoping, updated, and linked to the attacker's own record → cross-user disclosure + modification, plus a persistence bridge into your own views |
| Mongoid (CSFLE schema, two GHSAs) | GHSA-5qw9-p5cj-5jx4 / GHSA-p3g7-qcr8-3pgm | CVE-2026-93763 / CVE-2026-93764 | 6.5 | Client-side field-level-encryption schema generation omits fields declared on embedded models → values intended to be encrypted are stored in cleartext, with no error or warning. Anyone with ordinary DB/backup read access sees them. Silent security-feature failure |
| OpenStack Blazar (PUT/DELETE) | GHSA-4rvg-j972-3xw2 | CVE-2026-93854 | high | The policy authorize() wrapper builds the authz target by loading the lease under keyword lease_id, but the wsme_pecan controller delivers the route param positionally as id. The lookup returns None and authorization falls back to the requesting user's own project_id/user_id instead of the target owner → any authenticated user who knows a lease ID can modify/delete any tenant's leases. Fixed 17.0.1 |
| OpenStack Blazar (list) | GHSA-rf22-mxh9-4qvf | CVE-2026-93852 | high | GET /v2/leases returns every project's leases — no project scoping, no admin-only policy → lease IDs, reservation IDs, resource IDs, reservation metadata for all tenants. The enumerated IDs are the input for the mutation bypass above: list bug is the keychain for the write bug |
| Cotonti | GHSA-rw92-cwm5-wp25 | CVE-2026-93868 | 8.1 | Password-recovery tokens are md5(microtime()) — a space of ~10⁶ values per second. Read the server Date header, precompute candidates for a narrow window, probe them against the passrecover endpoint → reset any account including administrators, unauthenticated |
| Cotonti | GHSA-3v3g-3pf9-fgcf | CVE-2026-93872 | 7.5 | Comments-plugin EditAction base64-decodes the cb parameter and feeds it to unserialize() with no allowed_classes restriction → registered users with comment rights instantiate arbitrary PHP objects; file write / code execution via gadget chains |
| Azkaban | GHSA-wcv9-64xq-42pp | CVE-2026-93737 | 6.5 | ScheduleServlet's fetchSchedule action omits the project permission check every sibling action performs → any authenticated user reads any project's schedule config: execution times, cron expressions, flow parameters, notification email lists. Sibling-action authorization drift inside one servlet |
Folds on this run:
- http-cache-semantics cache-policy pair (CVE-2026-93750 + CVE-2026-93748/GHSA-f27v-pv5m-c5g6 + GHSA-ch52-4w7c-c8xp) → folded onto shared HTTP cache boundary testing:
_varyMatches()comparesVaryheader fields byte-for-byte instead of honoring wildcards, so a response fetched for one client can be served to another on a key the policy meant to exclude; separately, client-controlledmax-staledefeats security-zeroed cache entries — request the same URL with a largemax-staleand the library will happily return another user's zeroed-for-security entry, includingSet-Cookiesession material. New probe rows for the cache matrix: wildcard-Varyreplay andmax-stale: 86400replay against shared caches. - LightLLM
/pd_registerunauthenticated node registration (CVE-2026-93839/GHSA-644q-7756-5g89, 9.8) → folded onto the May 21 AI-runtime boundary page next to the LMDeploy DistServep2p_connectitem: the PD-disaggregation WebSocket register endpoint accepts arbitrary node registrations with crafted JSON and no peer-address validation. Register yourself as a decode/worker node and the router ships full user prompts to your socket; replace legitimate nodes for DoS; or steer the PD Master into issuing requests to internal addresses. Same operator rule as DistServe: inference-cluster internal service-registration channels are unauthenticated control planes — enumerate register/join/heartbeat endpoints before theorizing a model-side attack.
Tracked without publication: Netty HTTP/1 malformed-Transfer-Encoding and chunk-size post-digit-whitespace smuggling pair (GHSA-c599-gp9v-cp79 / GHSA-c9wq-39xx-437p — the request-smuggling battery on the July methodology page already covers crafted-TE and whitespace-in-framing primitives; re-sweep Netty-fronted stacks as part of the existing desync battery); Quarkus Qute {#eval} drops the parent template's content type so sub-template output bypasses escaping (CVE-2026-93432, XSS/JSON injection — same sub-renderer-loses-policy context class as Grav's sandboxed render carrying raw config arrays; fold only if a chain survives strict review); SiYuan trio — heading style attributes and notebook names rendered as raw HTML into the Electron renderer with Node.js access → stored XSS = OS command execution on open (CVE-2026-93923/93922; the desktop-app renderer-with-Node sink class is already documented; revisit when a PoC chain lands), plus getDynamicIcon template-injection metadata read for read-only tokens (CVE-2026-93921); Mongoid in-memory-query ReDoS (CVE-2026-93761) and the remaining Mongoid siblings; IBM Guardium Data Protection 12.2 ~40-advisory wave — headline records are unauthenticated insecure deserialization + reflective method dispatch on the CAS listener TCP 16017 (CVE-2026-82340/GHSA-mf93-58gc-8fqq, 9.8) and hardcoded master secrets recoverable from hardware_assess/obstore binaries by any low-privileged user (CVE-2026-84034/GHSA-qq6f-r3p5-823p, 8.8) — advisory-summary-only, no reproduction detail published, tracked as an appliance-audit reminder (sweep non-standard listener ports + setuid helper binaries when Guardium is in scope); IBM Sterling File Gateway unvalidated-SSO-header session mint (CVE-2026-75878/GHSA-652p-cfff-8crm, 9.1 — same trusted-identity-header class as the Sept 18 kcp item); IBM MQ/WebSphere/i Patch-wave (smuggling, DoS, auth-bypass singles, summary-only); vLLM allowed_token_ids validated against tokenizer length not model output (CVE-2026-93840) + Triton _bincount_kernel memory corruption (CVE-2026-93841) + SGLang unbounded-memory and bootstrap_room pair (CVE-2026-93838/93688 — DoS class); Totolink/Cotonti/cockpit/CM2507/PLANET/Bransys generic-product singles; sub-map-stale Undici sibling items.
Why this is worth an operator page¶
- Field-name positions are injection sites, not just value positions. The 9.8 Mongoid item reflects a caller-supplied field name into internal method resolution. Everyone tests
{"field": {"$ne": 1}}; far fewer test{"<method_name>": ...}when the library itself walks the key through reflection. For any ORM/ODM/serializer in scope, send key payloads shaped like internal API surface (__class__,class,send,destroy,delete,each, ruby-ish method names in the target language's idiom) and diff errors/outcomes against a plain-attribute key. - Test the query-grammar sink at the library, not just the endpoint. String criteria reaching
$where-style evaluation means an app that feels safe (Model.where(currentUser_filter)) is exploitable if one parameter is attacker-influenced. On open-source Ruby/Mongo stacks, grep for.where(,any_of,all_of,match,find_bycalls whose argument crosses from request handling; the May 11 MongoosesanitizeFilterpage is the JS twin — this cluster shows the same class surviving inside the ODM itself, so the endpoint-level "we use a framework helper" excuse is not a negative control. - Authorization wrappers that resolve their target by a name the router doesn't deliver fail open to the caller's own identity. Blazar's
authorize()looked forlease_idin a context that only ever carriedid; theNoneresult didn't deny — it rebuilt the target from the requester. Operator test that costs one request: as user B, PUT/DELETE an object whose ID you learned via a list endpoint, using the exact route shape. A 2xx means the authz target never contained the object owner. In open-source targets, grep policy-enforcer code fortarget_factory/authorizehelpers that fetch-by-keyword and check the keyword spelling against the route decorator. - Pair the read bug with the write bug before reporting. Blazar's unscoped
GET /v2/leasesis medium-value alone; chained into the ID-keyed mutation bypass it is full cross-tenant control. When your sweep finds an unscoped list, immediately replay its IDs against the object's update and delete verbs — and report them as one chain. - Clock-derived tokens are scriptable ATO, not theory.
md5(microtime())is ~1M candidates/second — cheap to precompute when the server leaks its clock via theDateheader. This is the same family already on the wiki (AVideouniqid()pairing codes, Easy Appointments salt+timestamp mail links, Fluent Forms entropy): for any recovery/verification/2FA token, ask what entropy source mints it; anything time-derived gets a window-probability script. Evidence shape: candidate-generator source + hit-rate table + one successful reset on your own lab account. unserialize()withoutallowed_classesis a class-instantiation oracle for authenticated low-privilege users. Sweep every user-influenced value that gets base64-decoded then deserialized (cookies,cb-style opaque params, export/restore formats). Even without a published gadget chain, arbitrary instantiation on a target with common frameworks installed is a reportable boundary — and the absence of the second argument is a two-line grep in open-source PHP.- Enumerate actions inside a handler, not just handlers. Azkaban's
fetchScheduleskipped the check every sibling action in the same servlet performed — the fix for one action's CVE historically touches only that action. For?action=/ servlet-dispatch apps, build the full action list from source or error fuzzing and run the authorization matrix per action, per project. - Silent encryption-schema failure is a data-at-rest finding with no alert. CSFLE skipping embedded-model fields means "encrypted" columns sit in cleartext forever. If you get any read access to a Mongo deployment advertised as using CSFLE, spot-check whether documented-encrypted fields are actually ciphertext — especially inside subdocuments/arrays. Same audit rule applies to any "policy generated from annotations" system (RBAC manifests, CSP headers built from decorators): verify the generator's coverage, don't trust the declaration.
Validation workflow (authorized scope only)¶
Disposable models and owned accounts only
Run ORM probes against a lab Mongo instance with synthetic documents and a disposable app; never probe production query paths — operator-injected $where and reflection sinks can mutate or destroy data (the 9.8 item's impact is record removal). Recovery-token scripts run only against accounts you own on a lab install. Deserialization probes use inert marker objects, never RCE chains.
- ORM grammar battery (lab). Against any app that forwards request data into query helpers: (a) value-position operators (
{"$ne":null}etc.); (b) string criteria where an object is expected; (c) key-position probes shaped like library internals; (d) nested-attributes payloads naming a foreign record ID. Record status/body/DB-side effect per case; the nested-attributes IDOR proof is a foreign row gaining an attribute linking it to your account. - Authz-target probe. For every object route taking
{id}: from a second tenant, PUT then DELETE an object ID obtained from a list endpoint. 2xx = authorization target never bound to the object. Then re-run the same verbs against your own object as positive control. - List-scoping check. Request every collection list as the lowest-privilege account; count foreign-tenant rows. Any unscoped list immediately feeds step 2.
- Token-entropy audit. Identify the token source in the recovery/reset flow (source read if open; else behavioral: request N tokens, compare prefixes/lengths, correlate with server
Date). For any time-derived candidate, script a bounded precompute window (minutes, not days) against your own lab account and report the hit rate. - Deserialization-sink grep (open-source PHP targets). Map values that reach
unserialize/unserialize-equivalents; confirmallowed_classesargument presence; where absent, instantiate a harmless lab class whose constructor writes a marker file (inert proof only). - Action-matrix enumeration. Extract every
action=/method of each servlet/dispatcher; run authenticated cross-project reads for each; build the 403-vs-200 table.fetchSchedule-style misses are the ones fixes forget.
Reporting checklist¶
- For ORM reflection/operator items, pin library version and the exact request that crosses the boundary; separate disclosure from destruction claims — the 9.8s here claim both, most apps only expose the read path.
- For authz-target fallback, include the positive control (own object, 2xx) beside the cross-tenant result (foreign object, 2xx) and the list-endpoint screenshot that yielded the ID; state the fix version (Blazar 17.0.1).
- For predictable tokens, report the entropy source, candidates-per-window, probe rate observed by any rate limit, and one owned-account reset — never a third-party account.
- For CSFLE cleartext, quote only a synthetic canary value and name the field path (e.g.
embedded[].secret_field); the finding is that the schema generator dropped the rule, not the data. - For cache-semantics folds, keep proofs on two synthetic callers with random canaries;
Set-Cookievalues in evidence must be fake.
September 20 follow-up: token-entropy and whitelisted-RPC siblings (3 GHSAs + Frappe)¶
- NivoCart predictable reset tokens (CVE-2026-94107, GHSA-g4x6-pg76-867r, 8.1):
forgotten.phpmints recovery codes assubstr(md5(mt_rand()), 0, 10)with no rate limit and no expiration.mt_rand()is a 32-bit-seeded PRNG — the 10-hex-char output is brute-forceable offline once you bound the request time from the serverDateheader; knowing an admin email = admin ATO. Same script shape as the Cotonti item above; the "no rate limit" wording means the window is as wide as you want it. - ezBookkeeping TOTP replay (CVE-2026-94112, GHSA-j95g-6g2h-3f33): passcodes are never invalidated after use — a captured code authenticates repeatedly for ~90 s. Audit axis for any 2FA flow: after a successful code use, replay it immediately on a second authorization attempt in your own lab account; success = one-time-code-is-not-one-time. A phishing/proxy position that sees one code gets a 90-second ATO lane, not a dead token.
- Dancer2 silent CSPRNG fallback (through 2.1.0, GHSA-ccc2-34g9-j7rg):
SessionFactory::generate_idsilently falls back to a SHA-1 overrand(), module path, counter, PID, memory address, and aList::Util::shufflestring (alsorand()-seeded) whenMath::Random::ISAAC::XS/Crypt::URandomare missing. Every input is low-entropy or observable (PID ranges, address layout). Durable rule for session-ID audits — a secure-default library can degrade silently on a thin deployment: collect N session IDs from a target, run entropy/duplication/sequence checks on the IDs themselves; don't trust the framework name. Same "silent security-feature failure" class as the Mongoid CSFLE cleartext item. - Frappe/ERPNext whitelisted-method permission drift (CVE-2026-94113, GHSA-m9g7-gc6x-55x7):
get_projectwise_timesheet_data,get_timesheet_detail_rate, andget_timesheetare@frappe.whitelist()callables that skip doctype permission checks → any authenticated user enumerates billable time logs, project names, and billing amounts. Frappe's entire RPC surface is/{app}/{method}over whitelisted functions — enumerate the callable registry (source grep on open installs;/api/method/probing on live ones) and run the authorization matrix per callable. This is the Azkaban sibling-action axis generalized: in RPC-registry frameworks, the route list is the function list, and decorators per function drift independently.
September 22 follow-up: order-timestamp-derived download tokens (Concrete CMS Community Store)¶
- Concrete CMS Community Store < 2.7.8 digital-download tokens (CVE-2026-95653, GHSA-4m3m-829w-5q37, 7.5): download tokens are derived from the order creation timestamp instead of a CSPRNG, and order/file IDs are sequential — unauthenticated attackers enumerate IDs, recompute tokens from the (often visible or range-bounded) order time, and pull other customers' purchased digital goods. This is the Cotonti
md5(microtime())pattern wearing an e-commerce costume: the derivation input is public or bracketable infrastructure metadata (order times appear on receipts, invoices, "ordered on" UI, and drift by at most a day across a batch). Sweep rule for every token-bearing URL (download, receipt, invoice, sharing link): ask what entropy source generated the token; if the answer correlates with anything the site itself displays, brute the ±1-day window × ID range offline against one lab-purchased item to prove the class without touching real customers' purchases.
September 22 follow-up: array-shaped credentials on the login form reach the SQL grammar¶
- Pagekit CMS ≤1.0.18 unauthenticated SQLi via
credentialsarray (CVE-2026-88756, GHSA-38cq-4qpv-62wf): the publicPOST /user/authenticateendpoint hands thecredentialsarray straight to the query layer, so operator-structured values (username[op]=...form-syntax arrays, JSON objects where the handler expects scalars) are parsed as query grammar instead of quoted strings → unauthenticated SQL injection from the login form itself. This is the SQL twin of the Mongoid caller-operator smuggle above and the Mongoose$neclass: the auth form is a query-builder sink. - Probe heuristic: on every login/register/forgot-password/2FA-verify field, re-submit with the scalar replaced by an array (
user[]=,user[eq]=, JSON{"user":{"op":"LIKE"}}variants) and diff responses/timing for grammar interpretation. Legacy PHP frameworks with array-to-WHERE helpers (Phalcon-family query builders, frameworkwhere(['column'=>$array])semantics) are the prone population. - EOL framing: Pagekit is archived/end-of-life — there is no upgrade path, so a live Pagekit instance with a public login form is an unpatched SQLi primitive; treat "CMS vendor archived the project" as a severity multiplier in reports, matching the stagnant-ML-loader rule from the June 30 model-parser page.
Related pages: the May 11 Mongoose sanitizeFilter page (JS twin of the operator-smuggle class), the Sept 18 ignored-scope-parameters page (list-vs-get authz diff and parameter-shadows-scope axes; Sterling SSO-header tracked item), the shared HTTP cache boundary testing page (http-cache-semantics fold target), the May 21 AI-runtime page (LightLLM fold target), and the Sept 5 AVideo page (time-derived token family).
September 23 follow-up: client-supplied sort columns cross into SQL through the pagination interceptor (MyBatis-Plus concatOrderBy)¶
- TDuckCloud tduck-platform ≤ 5.3 unauthenticated SQLi via
orders[0].column(CVE-2026-95829, GHSA-rj23-5g4f-59vx, exploit public): the app-levelPaginationInnerInterceptorconfig concatenates the client's requested sort column into theORDER BYclause (concatOrderBy) — sort parameters cannot be parameterized as bind values, so anything the interceptor doesn't whitelist rides into SQL text. This is the reusable Java/Spring leg of the query-grammar class: MyBatis-PlusPaginationInnerInterceptorwith a passthroughorderBy/columnfield is an ORDER BY injection sink, present in a very large installed base of admin-panel/form-builder software (tduck-family, ruoyi-family, and countless derived projects). Sweep rule for any Java/Spring target: (1) on every list/table endpoint, replace the sort parameter value with benign grammar probes ((select 1),(CASE WHEN (1=1) THEN id ELSE title END), timing functions) and diff result ordering/latency; (2) the derived-project population means the same vulnerable config line ships across many distinct vendor products — one tduck finding predicts sibling products built on the same scaffold; (3) ORDER BY position commonly defeats WAF/ORM escaping tuned for WHERE clauses, so test sort fields even when filter values test clean. Lab-bounded: read-only ordering/timing canaries on owned instances; the public exploit exists — do not replay third-party exploit bytes against production.
September 23 evening follow-up: uniqid() as a business-object identifier = unauthenticated guest-order PII reads (Isotope eCommerce ≤2.9.10)¶
- Isotope eCommerce (Contao) through 2.9.10 derives order identifiers from
uniqid()— a time-derived, non-CSPRNG hex string — and guest order lookup performs no ownership verification: supply a guessed/reconstructeduidand read billing address, customer data, and purchased files. Same script shape as Cotonti/AVideo/NivoCart above, new target class: order/quote/invoice/receipt identifiers on storefronts are enumerable identifiers, not just recovery tokens. Sweep rule: order numbers/references that look hex/timestamp-shaped (uniqid = 8-hex seconds + 5-hex microseconds) or sequential get a window-generation script before you conclude the guest lookup is safe; guest-facing lookup endpoints (order status, download links, invoice PDFs) must be tested with synthesized IDs, not just adjacent IDs, because "hard to guess" is the only defense these objects have when the ownership check is missing.
September 21 follow-up: library-level TOTP replay is the default, not the exception¶
- nginx-ignition TOTP reuse within the validity window (CVE-2026-61630, GHSA-hf33-q6cf-c66f): the app validates codes with the widely-used
pquerna/otpGo package, which does not track already-consumed codes — the maintainers' stated position (issue #61) is that single-use enforcement is each application's job, and most applications skip it. So the ezBookkeeping replay axis above is not a one-off bug but the library default: unless the app explicitly persists used codes per user per window, the code works repeatedly for ~30 s. Operator procedure upgrade: when you hold one TOTP from a phishing/proxy position, use it twice in your own lab-equivalent flow — first success proves capture validity, second success proves the app inherited the library default and you have a reuse lane, not a one-shot token. Report the class as "package X ships no consumption tracking; app Y adds none" with the package name in the finding — it predicts the same bug across every other app on that package.
October 2 follow-up: raw-metadata API leak + rand() auth token in one product (PictShare < 3.7.1)¶
- PictShare < 3.7.1 (CVE-2026-104051 / GHSA-8hvj-6v33-pcpf, 8.2): the
API::info()endpoint returns the complete raw metadata object with no field whitelist for any publicly visible file hash — including thedelete_codeauthorization token, uploader IP, User-Agent, remote port, and SHA-1. Public hash → delete_code → permanent deletion of arbitrary hosted files. PictShare < 3.7.1 (CVE-2026-104356 / GHSA-6654-r2c2-m62r, 5.9): independently,delete_codeis generated with non-cryptographicrand()(getRandomString(),src/inc/core.php), so codes are even guessable without the info endpoint. - Two reusable axes on one target class (public file hosts/shorteners/image boards): (1) read APIs that echo the stored record instead of a response schema leak their own authorization secrets — on every public
info/status/meta/oembed-style endpoint, diff returned field names against the fields the UI actually renders; anything invisible-but-returned (tokens, internal paths, uploader fingerprints) is the finding. (2) The same record usually carries a weak-derived capability token too: once you collect a few samples of an "authorization code", check alphabet/length/statistical structure — a 16-hex lowercase code in a PHP app isrand()-shaped and gets a state-recovery attempt. - Validate on owned installs with self-uploaded files: read your own record via the info API, prove delete_code presence, and prove the deletion path on your own upload only. Report class is "authorization secret in an unauthenticated read response + PRNG-derived token", both provable without touching other users' content.
October 4 follow-up: token-type confusion on refresh endpoints = restricted credential uplift (ezBookkeeping)¶
- ezBookkeeping 1.2.0 before 2.0.1 privilege escalation via
/api/v1/tokens/refresh.json(CVE-2026-105131, GHSA-983g-gj75-pg7f):TokenRefreshHandlernever checks the token type of whatever it is handed, so a short-lived or IP-restricted API token can be exchanged for a full 30-day session token — escaping the API token's expiry and IP allowlist entirely. This is the ezBookkeeping sibling of the TOTP-replay item above, and a new axis on this page: not weak entropy but credential-class uplift. Durable rule: a refresh/exchange/token-mint endpoint that accepts one credential family and returns another is a type-confusion sink — the handler trusts the shape of the input, not its class, so every restriction bound to the input class (lifetime, IP scope, scope list, audience) is silently dropped in the output. - Sweep rule for any product that ships both API tokens and session cookies: enumerate every token-lifecycle endpoint (
refresh,exchange,token,renew,convert) and replay each credential family through each one — API key into a session-refresh endpoint, read-scoped token into an admin-scoped mint, tenant-A token into a tenant-B exchange. Success = the response carries a longer-lived or broader-scope artifact than the input. The restricted-credential hardening many products added after token-leak incidents (IP allowlists, short expiry) is exactly the control this bug defeats: the uplift token inherits no restrictions at all, so treat "we issue short-lived keys" as unproven until you've run a low-privilege token through every sibling mint. - Same audit shape as the Sept 21 nginx-ignition/pquerna item and the Sept 20 Frappe decorator-drift item: restrictions live per-endpoint, not per-token, so every sibling path that touches the credential is its own authorization check. Lab-bounded: generate your own API token on an owned instance, refresh it, and diff the returned token's expiry/scope/IP behavior against the API token's — never refresh a token you did not mint.
October 6 follow-up: GridFS file-ID parameters consumed as query operators — MongoDB's own drivers (2 GHSAs)¶
- MongoDB PHP library GridFS (GHSA-8fxw-fmj8-xp7j / CVE-2026-88023) and pymongo (GHSA-8fvv-fgr5-f8ch / CVE-2026-88029): the GridFS read/delete/rename APIs accept an "id" that, when supplied as a structured document instead of a literal ObjectId, is interpreted as query criteria — disclosing arbitrary bucket file content, renaming the wrong file, or deleting all chunks in the bucket. Fixed by forcing exact-match on the id field.
- Durable axis, and this is the vendor eating its own dogfood: query-operator injection is the same class this page catalogs for Mongoid and Blazar — a value position that the data layer re-parses as structure. The API signature says
id, the implementation passes it tofind(). Operator sweep for any app/framework touching MongoDB: for every endpoint accepting a file/object identifier, send{"$ne": null},{"$gt": ""}, and{"$regex": ...}in place of the id/uuid/slug and diff the response against a literal-miss control; bucket-wide deletes from a single id parameter are the availability tell. Same probe applies to SQL layers accepting JSON bodies into ORM filters (the Sept 22 array-shaped-credentials leg above) — the grammar boundary is identical, only the parser differs.
October 6 follow-up: unescaped quotes in a Graph OData filter — directory lookup names reach the query grammar (SSSD Entra ID leg)¶
- SSSD with the Entra ID provider (GHSA-p2pv-jqxp-33qv / CVE-2026-104040, 4.4): user/group lookup names containing single quotes are not escaped before interpolation into Microsoft Graph OData
$filterexpressions, so a low-privileged local user (or any external identity whose name flows into a lookup) alters the query filter to broaden user/group searches — directory-object disclosure plus cache-population DoS. New grammar dialect on this page: OData string-literal escaping is the Graph twin of SQL string concatenation —$filter=(startswith(userPrincipalName,'x'))-style fragments built by concatenation break on an unescaped'exactly like'breaks a SQL literal. - Sweep rule extension: wherever the page's "value position re-parsed as structure" rule applies, the grammar list is now SQL, Mongo query docs, LDAP search filters, and OData
$filter/$searchfragments — anything a service assembles by string building from identity-derived data. Test inputs per dialect: quote, quote-pair withor 1eq1-shaped predicate, parenthesis balance break. On authorized Entra-integrated assessments, note that the identity name itself is the injection payload — a self-service-signup directory makes this externally reachable with no local foothold; proof stays with a synthetic account named with a benign quote canary and a broadened-search differential, never live directory mass reads.
October 6 16:0xZ follow-up: unescaped values in structured RediSearch TAG/TEXT filters (LangChain @langchain/redis)¶
GHSA-5x6v-p487-7qh2 / CVE-2026-105799 (@langchain/redis ≤1.1.0, fixed 1.1.1): the structured filter builders embedded attacker-influenceable values into RediSearch @field:{value} TAG / TEXT query strings with no escaping of RediSearch metacharacters (|, }, -, @, prefix/wildcard *, ~ fuzzy). A value closes the intended tag clause and appends its own — broadening @tenant:{a} to @tenant:{a|b} or negating/exiting the filter entirely. Where an app uses that filter as the tenant/document-access boundary over a vector index, results leak across scopes.
- Reusable axis, one step past the page's Mongoid/Blazar/GridFS legs: search-engine query DSLs are SQL. The fix (escape special chars + validate field names and filter types across both the fluent builder and custom-schema construction paths) is the standard parameterization equivalent, and the incomplete-fix sweep rule applies: enumerate every builder entry point, not just the documented one.
- Target-facing probe for RAG/agent stacks on Redis/OpenSearch/Elasticsearch: for each filter value you control, inject the engine's metacharacter set (
} {| - ~ * " \) and diff hit counts against a canary document in a second tenant's tag you planted. A count change from metacharacters alone = grammar injection before you ever attempt a cross-tenant canary read. Same presence-oracle evidence class as the Payload join-predicate leg promoted today — see the Oct 6 Payload wave page. - LangChain/RAG engagement checklist update: vector-store filter construction (RediSearch, pgvector metadata JSONB, Milvus boolean expr, Chroma
$where) is the access-boundary layer most RAG apps inherit unreviewed; test filter-value escaping per engine dialect, not "is there a filter".
October 7 03:30Z follow-up: ASUS RT-BE57 IFTTT pairing token from a predictable PRNG seed (CVE-2026-19396 / GHSA-fcpg-rqrm-22xm, high)¶
ASUS RT-BE57 router firmware minted IFTTT pairing tokens from a PRNG with a predictable seed; a nearby unauthenticated user who observes values from an administrator-initiated IFTTT pairing session can derive the pairing token and then read/modify router settings through the pairing interface. Same script shape as Cotonti/NivoCart/Isotope above — new twist worth naming: the observation corpus is not the server clock but the pairing session itself — token generation seeded from locally-observable state means any value the pairing flow exposes (QR content, local-network traffic, session timing) is seed material. Router/IoT pairing-audit rule: for every pairing/claim/claim-code flow, ask what entropy source seeds the code and what an on-network observer sees during mint; then test whether a captured session's observable values reproduce the token offline. Adjacent same-advisory-batch ASUS items tracked without publication: reflected-XSS family via crafted URL, config-file-upload stack overflow (memory-safety class), and DDNS credentials written to the system log (credential-in-log class already on the June 5 Admidio page).
October 8 06:31Z follow-up: per-record access hashes seeded from record creation time (Booking Calendar <11.8, CVE-2026-105193 / GHSA-x92h-fpqj-5752)¶
Booking Calendar derives its per-booking access hashes (the capability token embedded in guest booking-management links, gating read and in-place modify of the booking's personal data) from a low-entropy, time-seeded value instead of a CSPRNG. An unauthenticated attacker who can determine a booking's creation time — from the confirmation-email timestamp, booking page dates, or ordering of adjacent booking IDs — recomputes the hash offline and reads/modifies that booking.
- Axis extension on this page's Cotonti/NivoCart/ASUS lineage: the entropy-reduction tell is the carrier of the secret. A recovery token is one-shot and expires; a booking access hash is a standing capability embedded in user-visible links, so the offline-bruteforce window is permanent and every issued link is a corpus sample. When any opaque per-record token appears in a URL, ask: is it random or derived? Two samples of adjacent-created records exposing structural similarity (same prefix, monotonic middle) is an immediate derivation fingerprint — collect pairs from disposable accounts created at known times and test offline reproduction.
- Report shape: entropy source, derivation inputs recoverable from public data (timestamps, IDs), and one owned-record read/modify proof. The booking/reservation/cart/guest-link family in WP booking plugins is high-prevalence — hash-in-URL patterns deserve the derivation probe on every booking-style plugin, not just this one.
October 8 17:5xZ follow-up: chat-history session IDs consumed as Mongo query operators — the agent-stack twin of the GridFS leg (LangChain @langchain/mongodb, CVE-2026-106119 / GHSA-m6rx-h84q-8r95, @langchain/mongodb <=1.3.0 → 1.3.1)¶
MongoDBChatMessageHistory never enforced its documented string type for session identifiers at runtime: a structured sessionId (from any request field that reaches chat-history read/append/clear) is interpreted as MongoDB query criteria, giving an attacker read/modify/delete over other users' stored conversations in a shared collection. Fixed by runtime type validation + literal comparison.
- This is the Oct 6 GridFS-id leg wearing agent-framework clothes — and the highest-prevalence instance yet, because LangChain/RAG chat servers take
session_id/thread_idstraight off HTTP request bodies by design. Sweep rule update for any agent/chat product with server-stored history: for everysession_id/thread_id/conversation_idparameter, submit{"$ne":null}/{"$gt":""}/{"$regex":"^"}shapes in place of the string and diff against a literal-miss control; a cross-user conversation read from one operator-shaped ID is the tell. The operator battery on this page now spans SQL ORM filters, Mongo id positions, search DSL metachars, and OData/RediSearch grammars — the identifier positions in chat APIs are the newest and least-patched carrier. - Report shape: two disposable users, one shared collection, prove user A reads/modifies user B's messages with an operator-shaped session id, redact content.
October 8 20:1xZ follow-up: the database driver's own escaper is a grammar sink — MariaDB Connector/Node.js three-leg wave (CVE-2026-107385 / GHSA-r3rv-jm3r-62q2, CVE-2026-107384 / GHSA-v6pj-gxxw-phfw, CVE-2026-107383 / GHSA-48qf-xh34-q73r, all fixed 3.2.5/3.3.4/3.4.7/3.5.4)¶
The official MariaDB Node connector shipped two SQLi legs where the escaping layer itself was the injection site — the driver-level twin of this page's Mongoid cluster, and the reminder that "we use parameterized queries" is only as true as the escaper's model of the session.
- Escaper ignored server session state (107385, high). Text-protocol escaping always backslash-escaped quotes without consulting the session's
NO_BACKSLASH_ESCAPESSQL mode — the status flagSTATUS_NO_BACKSLASH_ESCAPESwas declared in the code and never read. Under that mode a backslash is ordinary and quotes double, so the escaped value closed the string literal and any placeholder-passed value became SQL.Connection.escape()included. Binaryexecute()/batch()unaffected (out-of-band params). Exposure requiresNO_BACKSLASH_ESCAPESdeliberately enabled (server-wide, connectorsessionVariables/initSql, or appSET sql_mode) — not implied by ANSI/ORACLE/TRADITIONAL on MariaDB 11.4. - Own escaper existed but wasn't called (107384, high). With non-default
permitSetMultiParamEntries, object keys expand into SET-clause column names via three hand-built backtick-quoting sites that wrote keys out unescaped —escapeId()(which correctly doubles backticks) existed since a 2023 fix for GitHub issue #252 but none of the three sites called it. A backtick in a key closes the identifier and the rest of the key parses as SQL, unconfined to an assignment. Passing a request body intoquery('UPDATE users SET ? ...', [body, id])is the normal reason the option exists. - Sibling-case parity + allocUnsafe leak (107383, high). GeoJSON Polygon/MultiPolygon binary encoders sized the buffer from a ring's
lengthproperty but only wrote real arrays: any non-array ring with a numericlengthreserved4+16*lengthunwrittenBuffer.allocUnsafe()bytes returned to the wire — uninitialized Node heap (other users' request bodies, session tokens, credentials, TLS key material) lands in a DB row, silent, persists to backups/replicas. The sibling LineString case aborted correctly. Gate is justvalue.typenaming a GeoJSON type.query()unaffected.
Operator rules:
- The escaper's grammar model must match the session's grammar mode. For any DB-stack target where you can influence
sql_mode-style session state (shared hosts, apps that SET modes, DBaaS configs), placeholder SQLi batteries must be run under each mode spelling — a payload family that fails on the default mode is not a negative control. Fingerprint pass on Node/MariaDB stacks: version-pin the connector against the fixed bands before concluding placeholders are safe. - Incomplete-fix genealogy is grep-visible:
escapeIdexists, N hand-built call sites ignore it. When auditing any escaper/quoting helper, enumerate call sites by string-building shape (backtick/quote concatenation), not helper existence — the same guard-coverage-diff rule as the PraisonAI tool-class parity leg. - Non-default option = deliberate target selection.
permitSetMultiParamEntriesexists precisely to take request bodies; the option-enablement question ("does this app pass bodies into SET ?") is answerable from open-source app code in advance. - Shared-process heap-to-row disclosure changes bug-bounty triage for GeoJSON/geometry params: prove on your own app with
{"type":"Polygon","coordinates":[{"length":4000}]}-shaped input, then read back the column bytes for structure, never on client data.
October 8 22:1xZ follow-up: Hazelcast data grid — client-supplied predicates execute server-side, and a low-priv client reads arbitrary member memory¶
Two advisories published 22:09–22:10Z against Hazelcast 5.6.0 (CE+EE, slim and full; fixed 5.7.0 / 5.6.1 / 5.5.10 / 5.4.5): a missing-authorization leg in the Predicates API lets a client execute arbitrary code on a member (GHSA-w294-6q5q-53p8 / CVE-2026-107725, 7.x), and a critical leg lets a low-privileged malicious client read arbitrary memory from any cluster member — Java heap, off-heap, JVM address space — with crash/EE-config corruption paths (GHSA-6v25-8wq6-xq4j / CVE-2026-107726, 9.8).
This is the in-memory-data-grid instance of this page's core rule — client-supplied query expressions are executable compute on the server, not data: Hazelcast ships user-defined predicates and indexed-extraction classes that the member runs on the client's behalf, so the same "query grammar crosses into interpreter" boundary as Mongo $where/Mongoid fragments, one layer down the stack. Operator axes: (1) on any engagement where a Hazelcast/Infinispan/Ignite-class grid port (default 5701 band, plus client protocol) is reachable — inference stacks, telco/finance caches, LLM-serving sidecars — client authorization granularity is the security model: enumerate what a minimally-authorized (or credentialed-but-unentitled) client can execute, not just read; the vendor's own hardening guide (client authorization + zero-config Compact serialization allowlist) is the confirmation that default posture is compute-on-behalf-of-client. (2) A cross-member memory-read primitive means cluster credentials are cache-tier secrets with heap-tier blast radius — session tokens, encryption keys, and other tenants' entries are all in the read scope; report reachability + version as the finding. (3) Deserialization/serialization-format fingerprinting (Compact vs Java vs identifiable) identifies the deployment family passively from the protocol handshake in a lab first, never against production grids.
October 9 follow-up: Guardium detail landed — the Sniffer is a pre-auth protocol-dissection surface reachable from the monitored network¶
The Oct 8 22:1xZ tracking note held the earlier Guardium ~10-pack as detail-free with a "fold-when-detail-lands" flag. Detail landed via the vendor bulletin: GHSA-qwfw-cq7q-5365 / CVE-2026-75875 (9.8 path-traversal RCE), GHSA-qpj5-hv25-xhp5 / CVE-2026-80381 (9.8 remote unauthorized SQL statement execution), GHSA-mmrc-x822-9c74 / CVE-2026-81932 (8.6 unauthenticated SQLi against the back-end database), GHSA-48h4-8xvm-v78r / CVE-2026-84058 (8.1 TDS (SQL Server) PRELOGIN packet decoder buffer overrun), plus stack/classic buffer-overflow RCE legs (CVE-2026-84035/84875/84198/84246/82895, 7.5–8.1) and a MongoDB SCRAM-username heap overflow in the MongoDB protocol parser (CVE-2026-82335, 8.1) — all in the Sniffer component of Guardium Data Protection 12.0/12.1/12.2, fixed by the SqlGuard_12.0p4018_SnifferUpdate patch (vendor workarounds: "None").
This upgrades this page's Sept 18 "sweep non-standard listener ports" reminder into a distinct and nastier boundary class:
- A passive-dissection appliance's attack surface is the monitored segment, not its admin interface. The TDS leg's own wording is the tell: "a remote attacker who can send a specially crafted TDS PRELOGIN packet to a network monitored by a Guardium Collector." No access to the appliance, no credentials, no route to its management IP required — anything that can put crafted protocol-shaped packets onto a spanned/tapped VLAN (any host the DB traffic traverses, any tenant on a shared managed network) reaches the parser. On engagements where database-monitoring appliances are in scope, enumerate what traffic paths feed them; the "attacker on the monitored network" position is often satisfiable from far lower ground than the appliance network itself.
- Protocol decoders are pre-auth parse surfaces with per-engine batteries. The two named decoders are exactly where you'd expect: TDS PRELOGIN (the first pre-handshake packet, always parsed) and MongoDB SCRAM username length. For any DB-firewall/monitor/DLP appliance, build a per-supported-engine early-packet fuzz battery (TDS PRELOGIN, MySQL handshake, PostgreSQL startup, Mongo OP_MSG/SCRAM, Oracle NTNR) instead of treating the appliance's own REST/SSH surface as the only target.
- Unauthenticated SQLi into the appliance's own backend (81932) + unauthorized SQL execution (80381) means the database-security product is itself a SQL-injection sink that reaches a database — audit-log and policy-store backends hold collection configs, credentials, and captured-statement history. Treat the appliance DB as a crown-jewel datastore during recon, not metadata.
- Fingerprint patch level from FixCentral IDs: the
SqlGuard_<release>p<build>_SnifferUpdatefix-ID band is enumerable from public FixCentral quickorder URLs (the bulletin links the exact one) — pre-engagement, that tells you which Sniffer patch a 12.x deployment may or may not carry before you touch anything.
October 13 follow-up: MongoDB itself — user-addressable internal aggregation stages, query-rewriter field leakage, and a batch-size UAF disclosure (5-leg coordinated wave, SERVER tickets public)¶
MongoDB published a five-leg aggregation-framework wave (published 2026-10-13T18:30Z): two mongos legs where an authenticated readWrite user names an internal aggregation stage that lacks authorization checks, forcing a subsequent stage to execute on the router under conditions it was not designed to handle → unhandled fatal abort of the mongos process (GHSA-ppm2-9qg4-qq47 / CVE-2026-96299, GHSA-56w2-wpqh-pq52 / CVE-2026-104005); a use-after-free when an internal aggregation stage combines with batchSize: 0 — query results can contain unrelated data from the server process memory (GHSA-8hjj-9fm5-xw5g / CVE-2026-96297); constant values mishandled during internal query rewriting so a read-privileged user receives field values the query never intended to return (GHSA-8w3x-ww8f-273q / CVE-2026-103965); and a collection-validator expression that stops the server when a document is later inserted — a stored-crash landmine (GHSA-v57p-wcq9-v96q / CVE-2026-104004).
This is this page's core rule — query expressions are executable semantics on the server — promoted from ORM layer to the database engine itself, with three new operator axes:
- "Internal" stage names are an allow-list assumption. The mongos legs' shared root cause: a pipeline stage intended only for internal/sharding plumbing is reachable because a user-supplied pipeline can name it, and it has no authorization checks of its own — the gate was "users won't call this." Sweep rule for any datastore/query engine with named pipeline operators (Mongo aggregation, Elasticsearch/_sql processors, SQL engine internal plan nodes): enumerate internal-named stages from server source or error-grammar (unknown-stage errors often enumerate valid names), then replay user-addressable pipelines naming each with the lowest privileged role. Second-generation recall: FalkorDB's
--boltflag parsed despite the feature gate (Sept 21) — config/feature gates disable listeners, not parsers; internal-stage gates are naming conventions, not ACLs. - Query-plan rewriting is a disclosure channel. The 103965 leg means the optimizer, not your predicate, decided which field values came back — an authorization/filter bypass wearing a planner costume. Field-level restrictions implemented above the engine (views,
$projectmasks, application ACL post-filtering) can be defeated below them. Probe shape: on an owned deployment, seed a canary field in a masked/filtered collection and hammer$expr/constant/conditional constructs through aggregation until unexpected values surface; a single unintended value is the finding. - Server-memory disclosure via parameter interaction, and stored-crash validators.
batchSize: 0+ internal stage = UAF returning other clients' heap residue — a read-privilege primitive that outgrows its CVSS label instantly (session tokens and other tenants' documents live in that heap). The validator leg inverts the usual shape: a write privilege plants a payload that detonates on someone else's later insert — treat collection validators like stored procedures in any DB engagement (create-validator → trigger-insert decision table, lab deployments only). mongos aborts are DoS-class; keep them to lab clusters, never sharded production.
October 14 09:31Z follow-up: Koha ILS — identifier positions in dynamic SQL (Criteria/GROUP BY/ORDER BY/LIMIT), prefix-normalized allow-lists, and the patched-twin differential (CVE-2026-105107 / GHSA-x89q-h3c4-g624, high)¶
- Koha Most-circulated items report SQLi:
reports/cat_issues_top.plconcatenates user-controlledCriteria,Filter, andLimitdirectly into dynamic SQL —Criteriais prefix-normalized but never allow-listed and lands in identifier positions (SELECT DISTINCTROW,GROUP BY,ORDER BY, main calculation query);Filtervalues are concatenated raw into single-quotedLIKE/BETWEENfragments;Limitis appended raw toLIMIT. A staff user with only the reports module permission (no superlibrarian) reads any table the DB user reaches —borrowers(userids, bcrypt hashes, two-factor secrets),api_keys,sessions. Fixed 24.11.19/25.05.14/25.11.08/26.05.03. Durable axes: (1) identifier positions are the WHERE-clause-blind spot — LIMIT, GROUP BY, ORDER BY, and select-list positions cannot be parameterized, so any framework that "normalizes" instead of allow-listing is the bug; this page's tduck ORDER BY leg generalizes: probe every SQL clause position with grammar payloads, not just sort; (2) prefix-normalization is not validation — a check that strips/validates a prefix then inserts the remainder verbatim is the check-before-normalize family with the polarity flipped; (3) the patched-twin differential is a free discovery map: the advisory itself names this as the unpatched twin ofreports/bor_issues_top.pl(CVE-2026-70372) — when one report/module gets a SQLi fix, immediately fuzz every sibling module that copies the same dynamic-SQL builder; paired-fix bands (24.11.19/24.11.20) reveal which bugs were found together. - Same product, same wave — command injection leg folded on the shell-quote page.
October 15 follow-up: TP-Link range extenders — reversible XOR-and-dictionary login obfuscation means a captured login yields a reusable alternate credential (CVE-2026-109506 / GHSA-v8j9-72vh-g46g, medium)¶
Multiple TP-Link Wi-Fi range extenders protect the HTTP web-management login with a custom reversible XOR-and-dictionary transformation instead of a hash. An unauthenticated adjacent attacker who captures one legitimate administrator login session (session key, dictionary, and login value) can derive a different credential value that produces the same accepted authentication result — a permanent password-equivalent that needs no further sniffing. Requirements: management login over plain HTTP plus one observed admin login.
- Durable axis: client-side reversible obfuscation of credentials is credential-equivalent material on the wire. Whenever a login flow "encrypts" the password in JavaScript with a device-supplied key/table (XOR, substitution dictionary, per-device alphabet), the server-side verifier stores the transformation result, not a one-way hash of the password — so any captured transformed value, and any pre-image derivable from the transformation, is a standing admin credential. Sweep rule for any embedded-device/management-UI target: capture one login, check whether the client transform is invertible with data the server sends (key, dictionary, per-session table are all usually fetched pre-login = invertible by design), then test replay of the captured value AND replay of a mutated pre-image that maps to the same output. Success on either = passive-network ATO class, report-worthy on adjacent-attacker scope.
- Fingerprint greps when firmware/source is available: dictionary/translation tables in web UI JS,
xorloops over password fields, and server-side verification that compares stored encoded values rather than hashing. Contrast with the correct shape (SRP/PAKE or server-issued-nonce hashing) — the ASUS IFTTT PRNG leg above is the predictable-token twin; this one is the invertible-transform twin where nothing is predictable, the transform itself is the leak. - Boundaries: own-network devices only, own admin login as the capture source, derived credential tested against your own device.
October 15 follow-up: TP-Link Omada Controller — default self-signed TLS certificate as the JWT signing key = forgeable admin sessions (CVE-2026-108336 / GHSA-3vfx-rq2g-mprf, critical)¶
Omada Controller signs its web-interface JWT access tokens with the SSL certificate — when the factory default self-signed certificate is left in place, the signing key is a public constant, so any attacker can sign admin JWTs and bypass login entirely. Same wave: reset-code endpoints with zero rate limiting (CVE-2026-108335, brute-force to admin ATO when password reset + email are configured), an MSP user-confirmation endpoint that takes a caller-controlled Identifier and returns distinguishable nonexistent/bad-password/success responses (CVE-2026-102298 — a standing credential-verification oracle for spraying, user enum included), and MSP/OpenAPI certificate-management APIs missing admin authorization (CVE-2026-107795 — low-privileged users can upload/delete the controller's TLS cert/key).
- Durable axis: fingerprint the default-cert-as-JWT-key shape from outside. For any appliance/controller web UI: grab the TLS certificate, check whether it is a vendor-default (shared serial/subject/key across the product line), then test whether session tokens are JWTs whose signature verifies under that public key. If both hold, admin access requires no exploit — just
jwt.signwith the device's own public cert material. Grep firmware/source for the JWT signing call sourcing the TLS keypair rather than a per-install random secret; the per-install-rotation question is the whole finding. - Second axis: management-plane oracles that "help" MSP multi-tenant flows. A confirmation/validation endpoint intended to check caller-supplied identities server-side becomes a password-verification oracle when responses differentiate outcomes; on MSP-mode controllers this chains with the default-cert leg for full fleet takeover. Decision-table proof (identical requests, three response classes) on your own lab controller only.
- Adjacent class notes: reset-code brute force and admin-scoped reset-code API disclosure (CVE-2026-108337) are the known no-throttle class — tracked, listed here only as same-product context.