Back to skill

Security audit

Slack Thread Export

Security checks for vulnerabilities and agentic risk

Overview

This skill is built for Slack exports, but it needs review because it bulk-accesses a logged-in Slack session and contains unsafe JavaScript construction in that authenticated context.

Install only if you are authorized to export the relevant Slack workspace data, keep channel/date/user scope narrow, and treat all CSV/JSONL outputs as sensitive. The publisher should fix the browser JavaScript interpolation and CSV formula handling before this is used on untrusted channel lists or shared exports.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

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. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export_slack_threads.py:43
Finding
CSV Formula Injection Through Exported Slack Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_slack_threads.py`, lines 43-54 and 156-161 **Vulnerability Type**: CSV formula injection **Risk Level**: Medium ### Vulnerable Code ```javascript const text = (item.text || '').replace(/\s+/g, ' ').trim(); rows.push({ datetime_utc: item.ts ? new Date(parseFloat(item.ts)*1000).toISOString() : '', channel_name: ch.name || channel, channel_id: ch.id || '', thread_ts: (() => { try { return new URL(permalink).searchParams.get('thread_ts') || ''; } catch(e) { return ''; } })(), username: item.username || '', text: text || '[non-text/attachment-only]', permalink, source_query_channel: channel, query, }); ``` ```python def write_csv(path: Path, rows: list[dict]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open('w', encoding='utf-8', newline='') as f: w = csv.DictWriter(f, fieldnames=FIELDS) w.writeheader() for row in sorted(rows, key=lambda x: (x.get('datetime_utc',''), x.get('channel_name',''), x.get('thread_ts',''))): w.writerow({k: row.get(k, '') for k in FIELDS}) ``` ### Technical Analysis Data obtained from Slack is written directly to CSV fields without neutralizing spreadsheet formulas. The Python CSV module correctly quotes and delimits CSV data, but CSV quoting does not prevent spreadsheet applications from interpreting a cell as a formula. Slack message text is controlled by workspace participants. Other exported fields, including usernames, channel names, permalinks, source channel names, and generated query values, may also contain or incorporate externally influenced content. A value beginning with a formula marker such as `=`, `+`, `-`, or `@` can be interpreted as a formula when the CSV is opened in compatible spreadsheet software. The vulnerability is triggered outside the Python process when an operator opens the generated file. Its behavior ther ...[truncated 1849 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sanitize every string field before writing spreadsheet-compatible CSV output, not only the message text. 2. Detect cells whose first significant character is `=`, `+`, `-`, or `@`. Depending on the intended consumers, also account for leading tabs, carriage returns, newlines, and spaces that spreadsheet software may ignore. 3. Prefix dangerous values with an apostrophe or another documented non-formula marker appropriate for the target spreadsheet application. For example: ```python FORMULA_PREFIXES = ('=', '+', '-', '@') def spreadsheet_safe(value: object) -> object: if not isinstance(value, str): return value candidate = value.lstrip(' \t\r\n') if candidate.startswith(FORMULA_PREFIXES): return "'" + value return value ``` Then apply it to all CSV fields: ```python w.writerow({ key: spreadsheet_safe(row.get(key, '')) for key in FIELDS }) ``` 4. Clearly document that the CSV is a presentation-oriented, formula-neutralized export. Preserve the JSONL file as the lossless raw representation. 5. If exact CSV text preservation is required, provide an explicit unsafe/raw CSV option with a prominent warning rather than making it the default. 6. Add tests covering all formula prefixes, leading whitespace, tabs, multiline values, usernames, channel names, query fields, and message text. 7. Advise users to import untrusted CSV files with formula execution disabled and to keep exported Slack archives in access-controlled locations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (9)

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill describes capabilities that can read browser session data, invoke network requests against Slack, write exported files, and run local scripts, but it declares no explicit tool scope or permissions boundary. That creates unnecessary ambient authority: an agent may execute broader file, shell, or network actions than the user expects, increasing the chance of over-collection or misuse of sensitive Slack session data.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill is explicitly designed to export Slack conversations and save raw JSONL audit/debug files, yet the user-facing description and workflow do not prominently require a privacy warning or consent check before collecting potentially sensitive workplace communications. In context, this is more dangerous because the workflow reuses a live logged-in browser session and accesses internal Slack search data, which can include confidential employee or business information at scale.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The notes describe operational guidance for exporting Slack thread data at scale, including retry and resume patterns, but do not include an explicit privacy, authorization, or data-minimization warning near the collection workflow. In the context of a skill specifically designed to extract conversations from a logged-in Slack session, this omission increases the risk of over-collection or unauthorized export of sensitive employee communications.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The script reads the Slack auth token from browser localStorage and uses it to issue authenticated requests against Slack's internal search API from a logged-in browser context. This enables bulk extraction of private workspace data using the user's session, expanding access and automation in a way that can bypass normal user awareness, consent, and administrative controls.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
'openclaw', 'browser', '--browser-profile', 'chrome', '--timeout', '120000',
        'evaluate', '--target-id', target_id, '--fn', fn, '--json'
    ]
    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode != 0:
        raise RuntimeError(r.stderr.strip() or r.stdout.strip())
    outer = json.loads(r.stdout)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script performs bulk export of Slack thread content and writes it to local files without any built-in privacy warning, sensitivity check, or confirmation gate. In this skill's context, the data source is a logged-in Slack session, so silent collection and storage materially increase the chance of exporting confidential workplace communications without informed user consent.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill writes every matched row to JSONL before applying the heuristic work-like filter used for CSV output, so sensitive non-work or over-collected content is still retained on disk. Users may believe filtering limits collection, but the raw export preserves the full dataset and increases exposure if files are shared, reused, or compromised.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return 0

    print(rendered)
    raise SystemExit(subprocess.run(cmd).returncode)


if __name__ == '__main__':
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
86% confidence
Finding
The default keyword list includes Korean terms alongside English ones, which bakes a specific language/locale assumption into the skill's filtering behavior. The file does not explain this locale constraint or provide an explicit opt-in mechanism for Korean-language defaults.

Static analysis

No suspicious patterns detected.