Advisory Details
Title: Elevated allowFrom sender-scope bypass via recipient and mutable sender metadata in openclaw-cn
Description:
Summary
openclaw-cn contains an authorization flaw in the /elevated directive path. A chat sender who has already passed the broader command gate can still enable elevated mode without matching the intended sender-scoped allowlist, because the elevated authorization helper also treats the recipient field (To) and mutable sender metadata (SenderName, SenderUsername, SenderTag) as valid matches for unprefixed tools.elevated.allowFrom.<provider> entries. This weakens the intended privilege boundary between ordinary command-capable chat users and the smaller set of users who should be allowed to activate elevated execution behavior.
Details
The vulnerable logic is in src/auto-reply/reply/reply-elevated.ts, inside isApprovedElevatedSender(). The function resolves the configured allowFrom entries for the active provider and then builds a token set from several MsgContext fields. The bug is that this token set mixes stable sender identity with unrelated or weak-trust fields:
addToken(params.ctx.SenderName);
addToken(params.ctx.SenderUsername);
addToken(params.ctx.SenderTag);
addToken(params.ctx.SenderE164);
addToken(params.ctx.From);
addToken(stripSenderPrefix(params.ctx.From));
addToken(params.ctx.To);
addToken(stripSenderPrefix(params.ctx.To));
Because unprefixed allowlist entries are compared against that entire set, a sender can be incorrectly approved when:
- The configured allowlist value matches the recipient/self address in
ctx.To, even though the actual sender is different.
- The configured allowlist value matches mutable profile metadata such as
SenderName, even though the stable sender identity is not allowlisted.
The vulnerable authorization decision is then consumed in src/auto-reply/reply/get-reply-directives.ts:
const elevated = resolveElevatedPermissions({
cfg,
agentId,
ctx,
provider: messageProviderKey,
});
...
if (directives.hasElevatedDirective && (!elevatedEnabled || !elevatedAllowed)) {
return { kind: "reply", reply: { text: formatElevatedUnavailableMessage(...) } };
}
If elevatedAllowed is incorrectly computed as true, the /elevated on directive is accepted and the session's elevatedLevel is updated instead of being rejected.
This was verified on the real code path used by getReplyFromConfig() and resolveReplyDirectives(). The current evidence is unit-level rather than full WhatsApp device E2E, but it exercises the live configuration loader, route resolution, directive parsing, elevated authorization helper, and session persistence without source modification or mocks of the vulnerable component.
PoC
Prerequisites
- A checkout of the affected
openclaw-cn repository.
python3 and bun installed locally.
- A message context that has already reached
CommandAuthorized: true.
- A configuration that uses
tools.elevated.allowFrom.whatsapp to restrict elevated access more narrowly than the general command-authorized surface.
Reproduction Steps
- Download the TypeScript driver from: driver.ts
- Download the verification harness from: verification_test.py
- Download the control harness from: control-normal-behavior.py
- From the repository root, run the verification harness:
python3 llm-enhance/cve-finding/similar/improper-privilege-management/Advisory-GHSA-f6h3-846h-2r8w-elevated-allowfrom-sender-scope-bypass-exp/verification_test.py
- Observe that both experiment cases succeed:
recipient_vuln: the sender is +15550001000, but tools.elevated.allowFrom.whatsapp=["+15550002000"] and To="+15550002000", causing elevated mode to be enabled.
mutable_vuln: the sender's stable identity is unchanged, but SenderName="owner-display-name" and tools.elevated.allowFrom.whatsapp=["owner-display-name"], causing elevated mode to be enabled.
- Run the control harness:
python3 llm-enhance/cve-finding/similar/improper-privilege-management/Advisory-GHSA-f6h3-846h-2r8w-elevated-allowfrom-sender-scope-bypass-exp/control-normal-behavior.py
- Confirm the protected baseline:
- when
To does not match the allowlist, /elevated on is denied;
- when
SenderName does not match the allowlist, /elevated on is denied.
Log of Evidence
Verification run:
Verification Mode: Unit-Test
Data flow: inbound DM config/state -> checkInboundAccessControl -> resolveWhatsAppCommandAuthorized -> getReplyFromConfig -> resolveReplyDirectives -> resolveElevatedPermissions -> session store elevatedLevel
[DEFECT CONFIRMED WITH LIMITATIONS] recipient-token and mutable-sender elevated allowFrom bypasses reproduced
Observed experiment behavior:
recipient_vuln returned Elevated mode set to ask (approvals may still apply). and persisted elevatedLevel: "on".
mutable_vuln returned Elevated mode set to ask (approvals may still apply). and persisted elevatedLevel: "on".
Control run:
Control Mode: Unit-Test
[CONTROL PASS] protected baseline preserved for both control cases
Observed control behavior:
recipient_control returned elevated is not available right now ... and left elevatedLevel: null.
mutable_control returned elevated is not available right now ... and left elevatedLevel: null.
Impact
This is an improper privilege management issue in the chat control plane. It impacts deployments that rely on tools.elevated.allowFrom.<provider> to create a narrower elevated-execution boundary than the general command boundary. A command-authorized but non-elevated sender can activate elevated mode by matching recipient routing or mutable sender metadata rather than a stable sender identity. In deployments where elevated mode unlocks stronger execution behavior, approvals, or host-side actions, this allows a lower-privileged chat actor to cross into a more sensitive execution state than intended.
Affected products
- Ecosystem: npm
- Package name: openclaw-cn
- Affected versions: <= 0.2.1
- Patched versions:
Severity
- Severity: Medium
- Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L
Weaknesses
- CWE: CWE-269: Improper Privilege Management
Occurrences
| Permalink |
Description |
|
function isApprovedElevatedSender(params: { |
|
provider: string; |
|
ctx: MsgContext; |
|
allowFrom?: AgentElevatedAllowFromConfig; |
|
fallbackAllowFrom?: Array<string | number>; |
|
}): boolean { |
|
const rawAllow = resolveElevatedAllowList( |
|
params.allowFrom, |
|
params.provider, |
|
params.fallbackAllowFrom, |
|
); |
|
if (!rawAllow || rawAllow.length === 0) return false; |
|
|
|
const allowTokens = rawAllow.map((entry) => String(entry).trim()).filter(Boolean); |
|
if (allowTokens.length === 0) return false; |
|
if (allowTokens.some((entry) => entry === "*")) return true; |
|
|
|
const tokens = new Set<string>(); |
|
const addToken = (value?: string) => { |
|
if (!value) return; |
|
const trimmed = value.trim(); |
|
if (!trimmed) return; |
|
tokens.add(trimmed); |
|
const normalized = normalizeAllowToken(trimmed); |
|
if (normalized) tokens.add(normalized); |
|
const slugged = slugAllowToken(trimmed); |
|
if (slugged) tokens.add(slugged); |
|
}; |
|
|
|
addToken(params.ctx.SenderName); |
|
addToken(params.ctx.SenderUsername); |
|
addToken(params.ctx.SenderTag); |
|
addToken(params.ctx.SenderE164); |
|
addToken(params.ctx.From); |
|
addToken(stripSenderPrefix(params.ctx.From)); |
|
addToken(params.ctx.To); |
|
addToken(stripSenderPrefix(params.ctx.To)); |
|
|
|
for (const rawEntry of allowTokens) { |
|
const entry = rawEntry.trim(); |
|
if (!entry) continue; |
|
const stripped = stripSenderPrefix(entry); |
|
if (tokens.has(entry) || tokens.has(stripped)) return true; |
|
const normalized = normalizeAllowToken(stripped); |
|
if (normalized && tokens.has(normalized)) return true; |
|
const slugged = slugAllowToken(stripped); |
|
isApprovedElevatedSender() builds the approval token set. The vulnerable logic adds ctx.To, SenderName, SenderUsername, and SenderTag into the same default-matching pool as stable sender identifiers, enabling recipient-token and mutable-metadata bypasses. |
|
const messageProviderKey = |
|
sessionCtx.Provider?.trim().toLowerCase() ?? ctx.Provider?.trim().toLowerCase() ?? ""; |
|
const elevated = resolveElevatedPermissions({ |
|
cfg, |
|
agentId, |
|
ctx, |
|
provider: messageProviderKey, |
|
}); |
|
const elevatedEnabled = elevated.enabled; |
|
const elevatedAllowed = elevated.allowed; |
|
const elevatedFailures = elevated.failures; |
|
if (directives.hasElevatedDirective && (!elevatedEnabled || !elevatedAllowed)) { |
|
typing.cleanup(); |
|
const runtimeSandboxed = resolveSandboxRuntimeStatus({ |
|
cfg, |
|
sessionKey: ctx.SessionKey, |
|
}).sandboxed; |
|
return { |
|
kind: "reply", |
|
reply: { |
|
text: formatElevatedUnavailableMessage({ |
|
runtimeSandboxed, |
|
failures: elevatedFailures, |
|
sessionKey: ctx.SessionKey, |
|
}), |
|
}, |
|
}; |
|
} |
|
resolveReplyDirectives() consumes the result of resolveElevatedPermissions(). When the flawed elevated authorization returns true, the /elevated directive is accepted instead of returning the denial response. |
Advisory Details
Title: Elevated
allowFromsender-scope bypass via recipient and mutable sender metadata inopenclaw-cnDescription:
Summary
openclaw-cncontains an authorization flaw in the/elevateddirective path. A chat sender who has already passed the broader command gate can still enable elevated mode without matching the intended sender-scoped allowlist, because the elevated authorization helper also treats the recipient field (To) and mutable sender metadata (SenderName,SenderUsername,SenderTag) as valid matches for unprefixedtools.elevated.allowFrom.<provider>entries. This weakens the intended privilege boundary between ordinary command-capable chat users and the smaller set of users who should be allowed to activate elevated execution behavior.Details
The vulnerable logic is in
src/auto-reply/reply/reply-elevated.ts, insideisApprovedElevatedSender(). The function resolves the configuredallowFromentries for the active provider and then builds a token set from severalMsgContextfields. The bug is that this token set mixes stable sender identity with unrelated or weak-trust fields:Because unprefixed allowlist entries are compared against that entire set, a sender can be incorrectly approved when:
ctx.To, even though the actual sender is different.SenderName, even though the stable sender identity is not allowlisted.The vulnerable authorization decision is then consumed in
src/auto-reply/reply/get-reply-directives.ts:If
elevatedAllowedis incorrectly computed as true, the/elevated ondirective is accepted and the session'selevatedLevelis updated instead of being rejected.This was verified on the real code path used by
getReplyFromConfig()andresolveReplyDirectives(). The current evidence is unit-level rather than full WhatsApp device E2E, but it exercises the live configuration loader, route resolution, directive parsing, elevated authorization helper, and session persistence without source modification or mocks of the vulnerable component.PoC
Prerequisites
openclaw-cnrepository.python3andbuninstalled locally.CommandAuthorized: true.tools.elevated.allowFrom.whatsappto restrict elevated access more narrowly than the general command-authorized surface.Reproduction Steps
python3 llm-enhance/cve-finding/similar/improper-privilege-management/Advisory-GHSA-f6h3-846h-2r8w-elevated-allowfrom-sender-scope-bypass-exp/verification_test.pyrecipient_vuln: the sender is+15550001000, buttools.elevated.allowFrom.whatsapp=["+15550002000"]andTo="+15550002000", causing elevated mode to be enabled.mutable_vuln: the sender's stable identity is unchanged, butSenderName="owner-display-name"andtools.elevated.allowFrom.whatsapp=["owner-display-name"], causing elevated mode to be enabled.python3 llm-enhance/cve-finding/similar/improper-privilege-management/Advisory-GHSA-f6h3-846h-2r8w-elevated-allowfrom-sender-scope-bypass-exp/control-normal-behavior.pyTodoes not match the allowlist,/elevated onis denied;SenderNamedoes not match the allowlist,/elevated onis denied.Log of Evidence
Verification run:
Observed experiment behavior:
recipient_vulnreturnedElevated mode set to ask (approvals may still apply).and persistedelevatedLevel: "on".mutable_vulnreturnedElevated mode set to ask (approvals may still apply).and persistedelevatedLevel: "on".Control run:
Observed control behavior:
recipient_controlreturnedelevated is not available right now ...and leftelevatedLevel: null.mutable_controlreturnedelevated is not available right now ...and leftelevatedLevel: null.Impact
This is an improper privilege management issue in the chat control plane. It impacts deployments that rely on
tools.elevated.allowFrom.<provider>to create a narrower elevated-execution boundary than the general command boundary. A command-authorized but non-elevated sender can activate elevated mode by matching recipient routing or mutable sender metadata rather than a stable sender identity. In deployments where elevated mode unlocks stronger execution behavior, approvals, or host-side actions, this allows a lower-privileged chat actor to cross into a more sensitive execution state than intended.Affected products
Severity
Weaknesses
Occurrences
openclaw-cn/src/auto-reply/reply/reply-elevated.ts
Lines 50 to 95 in 558f272
isApprovedElevatedSender()builds the approval token set. The vulnerable logic addsctx.To,SenderName,SenderUsername, andSenderTaginto the same default-matching pool as stable sender identifiers, enabling recipient-token and mutable-metadata bypasses.openclaw-cn/src/auto-reply/reply/get-reply-directives.ts
Lines 307 to 334 in 558f272
resolveReplyDirectives()consumes the result ofresolveElevatedPermissions(). When the flawed elevated authorization returns true, the/elevateddirective is accepted instead of returning the denial response.