Medium severity4.3NVD Advisory· Published Apr 23, 2026· Updated Apr 28, 2026
CVE-2026-41908
CVE-2026-41908
Description
OpenClaw before 2026.4.20 contains a scope enforcement bypass vulnerability in the assistant-media route that allows trusted-proxy callers without operator.read scope to access protected assistant-media files and metadata. Attackers can bypass identity-bearing HTTP auth path scope validation to retrieve sensitive media content within allowed media roots.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.4.20 | 2026.4.20 |
Affected products
1Patches
199ef3a63c584fix(gateway): require read scope for assistant media (#68175)
4 files changed · +104 −1
CHANGELOG.md+1 −0 modified@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai - Agents/fallback: recognize bare leading ZenMux `402 ...` quota-refresh errors without misclassifying plain numeric `402 ...` text, and keep the embedded fallback regression coverage stable. (#47579) Thanks @bwjoke. - Failover/google: only treat `INTERNAL` status payloads as retryable timeouts when they also carry a `500` code, so malformed non-500 payloads do not enter the retry path. (#68238) Thanks @altaywtf and @Openbling. - Agents/tools: filter bundled MCP/LSP tools through the final owner-only and tool-policy pipeline after merging them into the effective tool list, so existing allowlists, deny rules, sandbox policy, subagent policy, and owner-only restrictions apply to bundled tools the same way they apply to core tools. (#68195) +- Gateway/assistant media: require `operator.read` scope for assistant-media file and metadata requests on identity-bearing HTTP auth paths so callers without a read scope can no longer access assistant media. (#68175) Thanks @eleqtrizit. ## 2026.4.15
src/gateway/control-ui.http.test.ts+76 −0 modified@@ -294,6 +294,82 @@ describe("handleControlUiHttpRequest", () => { }); }); + it("rejects trusted-proxy assistant media file reads without operator.read scope", async () => { + await withAllowedAssistantMediaRoot({ + prefix: "ui-media-scope-file-", + fn: async (tmpRoot) => { + const filePath = path.join(tmpRoot, "photo.png"); + await fs.writeFile(filePath, Buffer.from("not-a-real-png")); + const { res, handled, end } = await runAssistantMediaRequest({ + url: `/__openclaw__/assistant-media?source=${encodeURIComponent(filePath)}`, + method: "GET", + auth: { + mode: "trusted-proxy", + allowTailscale: false, + trustedProxy: { + userHeader: "x-forwarded-user", + }, + }, + trustedProxies: ["10.0.0.1"], + remoteAddress: "10.0.0.1", + headers: { + host: "gateway.example.com", + "x-forwarded-user": "nick@example.com", + "x-forwarded-proto": "https", + "x-openclaw-scopes": "operator.approvals", + }, + }); + expect(handled).toBe(true); + expect(res.statusCode).toBe(403); + expect(JSON.parse(String(end.mock.calls[0]?.[0] ?? ""))).toMatchObject({ + ok: false, + error: { + type: "forbidden", + message: "missing scope: operator.read", + }, + }); + }, + }); + }); + + it("rejects trusted-proxy assistant media metadata requests with an empty scope set", async () => { + await withAllowedAssistantMediaRoot({ + prefix: "ui-media-scope-meta-", + fn: async (tmpRoot) => { + const filePath = path.join(tmpRoot, "photo.png"); + await fs.writeFile(filePath, Buffer.from("not-a-real-png")); + const { res, handled, end } = await runAssistantMediaRequest({ + url: `/__openclaw__/assistant-media?meta=1&source=${encodeURIComponent(filePath)}`, + method: "GET", + auth: { + mode: "trusted-proxy", + allowTailscale: false, + trustedProxy: { + userHeader: "x-forwarded-user", + }, + }, + trustedProxies: ["10.0.0.1"], + remoteAddress: "10.0.0.1", + headers: { + host: "gateway.example.com", + "x-forwarded-user": "nick@example.com", + "x-forwarded-proto": "https", + "x-openclaw-scopes": "", + }, + }); + expect(handled).toBe(true); + expect(res.statusCode).toBe(403); + expect(JSON.parse(String(end.mock.calls[0]?.[0] ?? ""))).toMatchObject({ + ok: false, + error: { + type: "forbidden", + message: "missing scope: operator.read", + }, + }); + }, + }); + }); + it("includes CSP hash for inline scripts in index.html", async () => { const scriptContent = "(function(){ var x = 1; })();"; const html = `<html><head><script>${scriptContent}</script></head><body></body></html>\n`;
src/gateway/control-ui.ts+26 −1 modified@@ -38,7 +38,12 @@ import { resolveAssistantAvatarUrl, } from "./control-ui-shared.js"; import { sendGatewayAuthFailure } from "./http-common.js"; -import { getBearerToken, resolveHttpBrowserOriginPolicy } from "./http-utils.js"; +import { + getBearerToken, + resolveHttpBrowserOriginPolicy, + resolveTrustedHttpOperatorScopes, +} from "./http-utils.js"; +import { authorizeOperatorScopesForMethod } from "./method-scopes.js"; const ROOT_PREFIX = "/"; const CONTROL_UI_ASSISTANT_MEDIA_PREFIX = "/__openclaw__/assistant-media"; @@ -307,6 +312,26 @@ export async function handleControlUiAssistantMediaRequest( sendGatewayAuthFailure(res, authResult); return true; } + const trustDeclaredOperatorScopes = + authResult.method !== "token" && + authResult.method !== "password" && + authResult.method !== "none"; + if (trustDeclaredOperatorScopes) { + const requestedScopes = resolveTrustedHttpOperatorScopes(req, { + trustDeclaredOperatorScopes, + }); + const scopeAuth = authorizeOperatorScopesForMethod("assistant.media.get", requestedScopes); + if (!scopeAuth.allowed) { + sendJson(res, 403, { + ok: false, + error: { + type: "forbidden", + message: `missing scope: ${scopeAuth.missingScope}`, + }, + }); + return true; + } + } } const source = normalizeAssistantMediaSource(url.searchParams.get("source") ?? ""); if (!source) {
src/gateway/method-scopes.ts+1 −0 modified@@ -66,6 +66,7 @@ const METHOD_SCOPE_GROUPS: Record<OperatorScope, readonly string[]> = { "node.rename", ], [READ_SCOPE]: [ + "assistant.media.get", "health", "doctor.memory.status", "doctor.memory.dreamDiary",
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/openclaw/openclaw/commit/99ef3a63c58440d53f8e45ad861b846032fcb036nvdPatchWEB
- github.com/advisories/GHSA-v8qf-fr4g-28p2ghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-v8qf-fr4g-28p2nvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-41908ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-scope-enforcement-bypass-in-assistant-media-routenvdThird Party AdvisoryWEB
News mentions
0No linked articles in our index yet.