T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/export_slack_threads.py:14
- Finding
- JavaScript Injection in Authenticated Slack Browser Context<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_slack_threads.py`, lines 14-19 and 92-101 **Vulnerability Type**: Browser-context JavaScript injection **Risk Level**: High ### Vulnerable Code ```python JS_TEMPLATE = r''' async () => { const channel = '__CHANNEL__'; const page = __PAGE__; const teamId = '__TEAM_ID__'; const userId = '__USER_ID__'; const after = '__AFTER__'; const before = '__BEFORE__'; ``` ```python def run_eval(target_id: str, channel: str, page: int, user_id: str, team_id: str, after: str | None, before: str | None) -> dict: fn = (JS_TEMPLATE .replace('__CHANNEL__', channel) .replace('__PAGE__', str(page)) .replace('__TEAM_ID__', team_id) .replace('__USER_ID__', user_id) .replace('__AFTER__', after or '') .replace('__BEFORE__', before or '')) cmd = [ 'openclaw', 'browser', '--browser-profile', 'chrome', '--timeout', '120000', 'evaluate', '--target-id', target_id, '--fn', fn, '--json' ] ``` ### Technical Analysis The channel name, Slack team ID, Slack user ID, and date arguments are inserted directly into JavaScript source code using string replacement. These values are placed inside single-quoted JavaScript literals without escaping quotes, backslashes, line terminators, or other JavaScript syntax. An input containing a single quote followed by JavaScript statements can terminate the intended literal and alter the function evaluated by Browser Relay. This is not shell injection—the subprocess command is correctly passed as an argument array—but it is code injection into the JavaScript evaluation layer. The injection is especially sensitive because the generated function executes inside an authenticated Slack browser tab. The page context can read Slack-origin local storage, including the `localConfig_v2` data used by the exporter, and can make requests using the active browser session. The retry workflow does not r ...[truncated 1880 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not interpolate raw values into executable JavaScript source. 2. Encode each inserted JavaScript value using a structural serializer such as `json.dumps()`: ```python fn = (JS_TEMPLATE .replace('__CHANNEL_JSON__', json.dumps(channel)) .replace('__PAGE_JSON__', json.dumps(page)) .replace('__TEAM_ID_JSON__', json.dumps(team_id)) .replace('__USER_ID_JSON__', json.dumps(user_id)) .replace('__AFTER_JSON__', json.dumps(after or '')) .replace('__BEFORE_JSON__', json.dumps(before or ''))) ``` The corresponding template placeholders must not be surrounded by additional quotes: ```javascript const channel = __CHANNEL_JSON__; const page = __PAGE_JSON__; const teamId = __TEAM_ID_JSON__; const userId = __USER_ID_JSON__; const after = __AFTER_JSON__; const before = __BEFORE_JSON__; ``` 3. Prefer a Browser Relay interface that accepts a fixed function and structured arguments separately, if supported. This prevents data from being parsed as source code. 4. Apply strict input validation as defense in depth: - Slack user IDs should match the expected Slack ID format. - Team IDs should match the expected Slack team ID format. - Dates should be validated as ISO `YYYY-MM-DD` values. - Channel names should be restricted to the supported Slack channel-name character set. 5. Treat channel and failed-channel files as untrusted input. Validate every entry after reading it, including files generated by earlier runs. 6. Add regression tests using quotes, backslashes, newlines, template syntax, and attempted statement injection to verify that all values remain inert strings. ]]>
