The Session ID Is the Only Credential: Three Wukong CVEs Expose AI Chats and HR Records

Three NVD records published within four seconds of each other on 11 October 2026 (02:16:36Z–02:16:39Z), all assigned by disclosure@vulncheck.com, all credited to one researcher, Yaqi Chao, against two projects from the same vendor family. Wukong AICRM (2,135 stars) contributes an AI-chat session takeover; Wukong_HRM (1,591 stars) contributes a 9.3 unauthenticated bypass and an 8.7 universal-admin flaw. The shared theme is one this site keeps documenting: identity arrives from the caller — a session ID, a missing header, an empty user object — and the server fills in the most privileged default. None of the three records names a fixed version.

Wukong AICRM: your AI conversation is anyone’s sessionId

CVE-2026-108689 — 5.3 medium, CWE-639, CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N, affecting versions through 20260610 (the 10 June 2026 release, still the newest). POST /chat/send accepts a caller-supplied sessionId and writes the caller’s message into that session — including sessions owned by other users — then streams back an assistant reply built from the victim’s conversation history. The advisory describes the reply as grounded in the victim’s last 20 messages, which makes this a read primitive wearing a write’s clothes: append to someone’s AI chat, and the model reads their history back to you. The attacker also consumes the victim’s AI quota and rewrites the session title.

We read streamChat first-hand at the pinned commit (cdd1fbc3). The session comes straight from the request body, the caller’s identity is resolved alongside it — and the two are never compared:

public Flux<String> streamChat(ChatSendBO sendBO) {
    Long sessionId = sendBO.getSessionId();
    String content = sendBO.getContent();
    ...
    Long currentUserId = UserUtil.getUserIdOrNull();
    if (currentUserId != null) {
        AiContextHolder.setContext(sessionId, currentUserId);
        ...
    }
    Long messageId = saveMessage(sessionId, "user", content);

The current user is known. The session owner is never loaded. The message is persisted under whatever sessionId arrived on the wire. The researcher notes, which we read as a cross-check on mechanism, add that the session lookup elsewhere in the class checks existence only, while the same class already contains the correct ownership comparison in updateSessionPin — the fix pattern exists in the file, it just was never applied to the chat path — and that no chat route carries a permission annotation, with the chat session mapper absent from the row-level data-permission handler. Only “authenticated” applies, and authentication without ownership is the whole finding.

This is the fourth caller-supplied-identity chat flaw in a week of this site’s coverage, after JeecgBoot’s AI chat-memory read via username parameter and the WanWu/Astron batch — see JeecgBoot’s second wave and the caller-chooses-the-owner batch. The AI feature is new; the missing ownership check is the oldest bug in multi-tenant software.

Wukong_HRM: omit the header, receive the kingdom (CVE-2026-108707)

CVE-2026-108707 — 9.3 critical, CWE-287, 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, through commit 186115e (11 September 2026, still HEAD). The authentication aspect that fronts every HRM controller reads the AUTH-TOKEN header — and when the header is absent or unknown, it builds an empty user and waves the request through. The entire defect is visible in ParamAspect.java, which we read at the pinned commit:

String token = request.getHeader(Const.DEFAULT_TOKEN_NAME);
UserInfo info = new  UserInfo();
if (StrUtil.isNotEmpty(token) && redis.exists(token+Const.TOKEN_CACHE_NAME)) {
    info = redis.get(token);
}
if (ObjectUtil.isNull(info)) {
    throw new NoLoginException();
}
UserUtil.setUser(info);
...
return point.proceed();

new UserInfo() is never null, so the NoLoginException branch is dead code on the no-token path: point.proceed() executes with an empty user. Omit one header and every HRM API endpoint answers — payslips, salary history, employee personal data, attachment downloads, company-wide record modification and deletion, all unauthenticated.

Wukong_HRM: every employee is the HR administrator (CVE-2026-108708)

CVE-2026-108708 — 8.7 high, CWE-862, CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N, same ceiling commit. Even a legitimately authenticated low-privileged employee inherits the HR administrator role, because EmployeeAspect staples it onto every caller unconditionally — read first-hand at 186115e:

String mobile = UserUtil.getUser().getMobile();
EmployeeInfo employeeInfo = employeeService.queryEmployeeInfoByMobile(mobile);
if (employeeInfo == null) {
    employeeInfo = new EmployeeInfo();
}
AdminRole adminRole;
adminRole = new AdminRole();
adminRole.setLabel(91);
employeeInfo.setRole(adminRole);
EmployeeHolder.setEmployeeInfo(employeeInfo);
return point.proceed();

No role lookup, no branch: label 91 for everyone. Combined with the data-scope checks that return all employees (per the advisory), any employee reads payslips, salary records, bank cards and personal data, edits bank cards, and deletes employees, departments and contracts company-wide.

Our editorial reading is that the two HRM findings compose. The 108707 bypass delivers an empty user past authentication; the 108708 aspect then dresses that empty user in the admin role without consulting anything the user carries. Whether an empty mobile resolves cleanly through the employee query in every deployment is the one link we did not runtime-test — but nothing in either aspect inspects the caller’s actual privileges, so the composition follows from the code as written. A 9.3 and an 8.7 that multiply rather than add.

Coordinated disclosure, no patch, and three more PoCs waiting

The fix-status ledger is short: all three records describe the newest release or HEAD of their project, and no fixed version appears anywhere. Both repositories merged a “docs: add coordinated security disclosure policy” commit on 11 September 2026 — the disclosure came through the front door a month later, via VulnCheck, with working proof-of-concept scripts public in the same disclosure folder. Process worked; patches have not followed.

One detail worth recording precisely: the AICRM disclosure folder contains three further PoC scripts — custom-field uniqueness validation, employee-directory and menu-tree reads, project role-permission reads — with no CVE attached to any of them in NVD as of this writing. They may be findings still in the pipeline, or duplicates folded into the published record; we verified the filenames exist, and we claim nothing about their severity.

What is configurable without a patch: keep both applications off unauthenticated network paths and enforce authentication at the reverse proxy so a missing header never reaches ParamAspect; treat every AI chat session ID as a bearer token and audit chat_message rows for authors who do not own their sessions; restrict HRM attachment and export endpoints to named service accounts; and assume employee PII in Wukong_HRM is readable by every authenticated account — and possibly by none — until a release supersedes 186115e. The same closing line as PraisonAI, openPDC, JeecgBoot and the rest of this week’s ledger: the advisory is correct, there is no version to upgrade to, and the working exploit is already public.

Verification note. All CVE identifiers, CVSS 4.0 base scores and vectors, CWE assignments, publication timestamps (02:16:36Z–02:16:39Z on 11 October 2026), NVD status values and the assigning source (disclosure@vulncheck.com) were read first-hand from the NVD 2.0 API. Advisory dates (10 October 2026), affected ranges and the credit to Yaqi Chao were read from the three VulnCheck advisory pages. Every source excerpt — AICRM’s streamChat at cdd1fbc3, HRM’s ParamAspect and EmployeeAspect at 186115e — was read directly from the referenced repositories at the pinned revisions and is quoted verbatim. Star and fork counts, push timestamps, HEAD commits and the disclosure-folder listing came from the GitHub API. The PoC header details on getRequiredSession, updateSessionPin and the route-permission layout are the researcher’s stated mechanism, read first-hand and used as a cross-check, not independently re-derived line by line. The composition argument and the closing assessment are our editorial analysis. We found no evidence of exploitation of any of these and do not imply any. No request was sent to any third-party instance of any product named here, and no proof-of-concept was executed.

Sources: