---
id: 2026-10-xerj-console-api-bearer-token-mints-successor-tokens-cve-2026-97406-fixed-rc-98
title: "Xerj Console API: a bearer token could mint successor API tokens past the passkey gate (CVE-2026-97406, 9.1); fixed v1.0.0-rc.98"
date_disclosed: 2026-10-20
last_updated: 2026-10-30
severity: high
status: patched
ecosystems: [rust, mcp, search-engine, agent-infrastructure]
tools_affected: ["Xerj (xerj-org/xerj) Console API, v1.0.0-rc.11 per the record, every release before v1.0.0-rc.98 per the maintainer", "agents and MCP clients (Claude Code, Cursor and others the README lists) that hold a Xerj API token"]
tags: [cve, cert-cc, xerj, mcp-server, agent-search, authentication-bypass, token-minting, passkey, persistence, vendor-unresponsive, fixed-in-release, no-token-revocation]
---

## TL;DR
Xerj, a Rust search engine and MCP server for agents (17.7k stars), accepted an API bearer token on the Console API route that mints new API tokens, a route meant to require a passkey-authenticated browser session, so anyone holding one leaked token could mint persistent successor tokens that revoking the original would not touch (CVE-2026-97406, CERT/CC VU#645818, 2026-10-20; GitHub Advisory Database 9.1 Critical, unreviewed). The vendor did not answer CERT/CC's 2026-08-13 notice and the note still says no patch, but PR #2003 (merged 2026-10-21) gates the minting, revocation, invitation, passkey-deletion and logout routes behind a passkey session by default, and the maintainer states that v1.0.0-rc.98 (2026-10-22) carries it. Upgrade to rc.98 or later and rotate every API token issued before the upgrade, because the fix does not revoke tokens minted through the gap.

## What happened

- **2026-08-13.** CERT/CC notifies the vendor (the note's "vendor notified" date). No reply.
- **2026-10-20.** CERT/CC publishes VU#645818 and the CNA record CVE-2026-97406: "XERJ Console API bearer tokens can bypass the passkey-only authentication requirement and directly invoke the API-token creation endpoint, allowing unauthorized minting of persistent successor credentials." Credit: Shiran Guez. The note says the platform "lacks hierarchical revocation", so revoking a parent token does not invalidate its descendants, that a stolen owner token keeps full administrative authority, and that an attacker could issue an invitation, redeem it, and enrol their own passkey. The record's `affected` lists one version, `v1.0.0-rc.11`, with no range and no fix. The GitHub Advisory Database entry GHSA-vjpr-qxjc-7crv (published 2026-10-20, updated 10-29) prints CVSS 3.1 9.1 `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N`, CWE-306, and "Unknown" for package and versions.
- **2026-10-21.** PR #2003, "fix(console-api): require a passkey session to mint API tokens (CVE-2026-97406)", merges as commit 050a12e. Per the PR and the repository's `docs/SECURITY_MODEL.md`, a bearer token now gets 403 before the handler runs on `POST /auth/api-tokens`, `DELETE /auth/api-tokens/:id`, `POST /auth/magic/issue`, `DELETE /auth/passkeys/:id` and `POST /auth/logout`; ordinary reads and writes keep working on a bearer token. The check sits in the session extractor with no flag, so it is on by default. The same day, issue #2020 asks the maintainer for a vendor statement to CERT/CC, a release carrying the commit, and a GHSA, and notes that "the fix does not revoke tokens or passkeys minted before the upgrade."
- **2026-10-22.** v1.0.0-rc.98 is released; the maintainer's 2026-10-23 comment on #2020 says it contains the fix commit.
- **2026-10-23.** The maintainer posts a statement on the issue, not to CERT/CC: "this is a low-risk issue. It did not downgrade security, and exploiting it required an already-leaked token", and closes the issue as resolved.
- **2026-10-27.** CERT/CC revises the note (revision 2); it still reads "no patch, vendor unreachable". v1.0.0-rc.100 is released the same day; its notes do not mention the fix. The vendor tab on the repository has no advisory; the GHSA id is the database's own.

**Severity.** Two readings disagree. The database prints 9.1 Critical (unauthenticated in the vector); the maintainer calls it low-risk because the path starts from a token the attacker already holds. This file says `high`: the precondition is real, but a Xerj token is exactly what an agent or MCP client stores in its config, and the bug turned a revocable leak into persistent access. `status: patched` rests on a default-on gate in a merged commit and the maintainer's statement that rc.98 carries it; the release notes of rc.98 and rc.100 do not name it, and this file did not diff the tag.

**Why it matters.** Xerj is built to sit behind agents ("Works with Claude Code, Cursor and any MCP client"), which means its tokens live in `~/.claude.json`, `.cursor/mcp.json` and CI environments, the places every credential-stealing package this corpus tracks reads first. A stolen token that can mint its own successors survives the rotation the owner thinks they did.

## Am I affected?

```bash
xerj --version 2>/dev/null                                   # anything before v1.0.0-rc.98
grep -rn 'xerj' ~/.claude.json .cursor/mcp.json .mcp.json 2>/dev/null   # where a token would sit
```

- Any Xerj instance whose console was reachable on a release before rc.98, and any API token issued before the upgrade, is in scope. The Console API's token list (`GET /auth/api-tokens`, metadata only) shows tokens you did not mint.

## If you are affected

1. Upgrade to v1.0.0-rc.98 or later (rc.100 is newest as of 2026-10-30).
2. From a passkey session, list and revoke every API token, then issue new ones; the fix does not revoke tokens minted before it. Remove passkeys you did not enrol.
3. Treat anything the instance indexed as read by whoever held the token: [if an MCP server was malicious](../playbooks/if-an-mcp-server-was-malicious.md) covers the config-file and token rotation steps for the clients.

## Prevention

- Bind the console to loopback or an authenticated proxy; the README's default is loopback: [MCP hygiene](../prevention/mcp-hygiene.md).
- Scope and rotate the tokens agents hold: [credential hygiene](../prevention/credential-hygiene.md).

## Sources
- [CERT/CC — VU#645818: Xerj Console API authentication bypass leads to unauthorized credential minting](https://www.kb.cert.org/vuls/id/645818) — fetched 2026-10-30: published 2026-10-20, revised 2026-10-27, vendor notified 2026-08-13, the mechanism, the invitation and passkey-enrolment consequence, "no patch", "vendor could not be reached", credit Shiran Guez.
- [CVE Services — CVE-2026-97406](https://cveawg.mitre.org/api/cve/CVE-2026-97406) — fetched 2026-10-30: CNA certcc, published 2026-10-20, updated 10-29, affected `v1.0.0-rc.11`, the note as the only reference, no metrics.
- [GitHub Advisory Database — GHSA-vjpr-qxjc-7crv](https://github.com/advisories/GHSA-vjpr-qxjc-7crv) — fetched 2026-10-30: unreviewed, 9.1 and vector, CWE-306, package and versions unknown, EPSS 0.223%.
- [xerj-org/xerj — PR #2003](https://github.com/xerj-org/xerj/pull/2003) — fetched 2026-10-30: merged 2026-10-21 as 050a12e, the five gated routes, no flag, the CVE and VU references.
- [xerj-org/xerj — issue #2020](https://github.com/xerj-org/xerj/issues/2020) — fetched 2026-10-30: opened 2026-10-21, the four asks, the maintainer's 2026-10-23 comments (rc.98 carries the fix; "low-risk"), closed 2026-10-23.
- [xerj-org/xerj — docs/SECURITY_MODEL.md](https://github.com/xerj-org/xerj/blob/main/docs/SECURITY_MODEL.md) — fetched 2026-10-30: the "Console credentials: a passkey session is not an API token" section citing the CVE and VU, the route table, "A bearer token cannot mint a descendant".
- [xerj-org/xerj — releases](https://github.com/xerj-org/xerj/releases) — fetched 2026-10-30: v1.0.0-rc.100 dated 27 Oct marked Latest, notes without the fix; the tab otherwise lists corpus packs. [Security advisories tab](https://github.com/xerj-org/xerj/security/advisories): none published.
- [xerj-org/xerj — repository](https://github.com/xerj-org/xerj) — fetched 2026-10-30: "AI search engine and MCP server for bots and agents", 17.7k stars, "Works with Claude Code, Cursor and any MCP client", loopback default, Apache-2.0.
