Back to skill

Security audit

Skywork Excel

Security checks for vulnerabilities and agentic risk

Overview

The skill appears purpose-built for Skywork spreadsheet/report generation, but it sends user files to a remote service and contains credential and file-write handling flaws that users should review before installing.

Install only if you are comfortable sending selected spreadsheets, PDFs, images, CSVs, and prompts to Skywork's backend. Use a scoped/rotatable API key, do not print or paste it into logs or chats, avoid overriding --base-url, and run the skill in a workspace where downloaded files and logs cannot overwrite important user files.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/excel_api_client.py:520
Finding
API Key Disclosure Through Unrestricted Backend URL Override<![CDATA[ ## Vulnerability Details **File Location**: `scripts/excel_api_client.py`, lines 140-171 and 520-521 **Vulnerability Type**: Credential disclosure through an attacker-controlled network destination **Risk Level**: High ### Vulnerable Code ```python self.base_url = base_url.rstrip("/") # Get api key: explicit > env var if api_key is not None: self.api_key = api_key else: self.api_key = _get_api_key_auto() self.timeout = timeout if not self.api_key: raise ValueError("SKYWORK_API_KEY is required (set env or pass api_key=)") self._headers = {"Authorization": f"Bearer {self.api_key}"} ``` ```python def _build_request( self, url: str, method: str = "GET", headers: Optional[dict] = None, data: Optional[bytes] = None ) -> urllib.request.Request: """Build a urllib request with merged headers.""" request_headers = {**self._headers} if headers: request_headers.update(headers) return urllib.request.Request(url=url, data=data, headers=request_headers, method=method) def _urlopen( self, request: urllib.request.Request, timeout: Optional[int] = None ): """Open a URL request with configured timeout.""" return urllib.request.urlopen(request, timeout=timeout or self.timeout) ``` ```python parser.add_argument("--base-url", default=SKYWORK_GATEWAY_URL, help="Backend service URL") ``` ### Technical Analysis The client accepts an unrestricted `--base-url` value while `_build_request()` automatically attaches the Skywork Bearer credential to every request. There is no scheme validation, hostname allowlist, origin validation, or redirect-target validation before transmitting the credential. Although sending the API key to the declared Skywork service is necessary for the Skill, allowing the same credential to be sent to an arbitrary caller-selected destination exceeds minimum privilege. The initial health-check request is sufficient to disclose the credential; successful ...[truncated 1053 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--base-url` option from production builds unless custom backends are an explicit requirement. 2. Enforce an allowlist of exact HTTPS origins, such as `https://api-tools.skywork.ai`, before constructing any authenticated request. 3. Parse URLs with `urllib.parse.urlsplit()` and validate the scheme, normalized hostname, and effective port. 4. Do not attach `Authorization` by default to every request. Add it only after confirming that the request destination matches an approved origin. 5. Disable automatic redirects for authenticated requests, or validate every redirect destination and strip authorization headers whenever the origin changes. 6. Reject URLs containing user information, non-HTTPS schemes, unexpected ports, or ambiguous hostname encodings. 7. Add tests proving that credentials are not sent to unapproved hosts or cross-origin redirect targets. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/excel_api_client.py:581
Finding
Arbitrary File Overwrite Through Server-Controlled Output Filename<![CDATA[ ## Vulnerability Details **File Location**: `scripts/excel_api_client.py`, lines 415-427, 487-498, and 577-586 **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code The output filename is received from the remote backend: ```python elif event_type == "output_files": # Final output files output_files = event["files"] if verbose: write_log(f"\n📁 Output files ({len(output_files)}):") for f in output_files: oss_url = f.get('oss_url') if oss_url: write_log(f" - {f['name']} ({f['size']:,} bytes)") write_log(f" ☁️ OSS: {oss_url}") else: write_log(f" - {f['name']} ({f['size']:,} bytes) id={f['file_id']}") ``` The downloaded content is written with truncation semantics: ```python def download_file(self, file_id: str, save_path: str) -> None: req = self._build_request(f"{self.base_url}/api/download/{file_id}") with self._urlopen(req, timeout=60) as resp: content = resp.read() with open(save_path, "wb") as f: f.write(content) print(f"💾 Downloaded: {save_path} ({len(content):,} bytes)") ``` The server-controlled name is directly combined with the local output directory: ```python for f in output_files: save_path = os.path.abspath(f"{final_output_dir}/{f['name']}") oss_url = f.get('oss_url', '') try: client.download_file(f["file_id"], save_path) write_log(f" ✅ {f['name']}") write_log(f" 📁 Local: {save_path}") ``` ### Technical Analysis The backend controls `f["name"]`, but the client does not reject absolute paths, parent-directory components, path separators, or symbolic-link targets. Calling `os.path.abspath()` only normalizes the path; it does not ensure that the result remains inside `final_output_dir`. For example, a backend-provided filename such as `../../.bashrc` can resolve outside the output ...[truncated 1270 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every backend-provided filename as untrusted. 2. Reject absolute paths, `..` components, drive prefixes, null bytes, and both Unix and Windows path separators. 3. Reduce the value to a safe basename and enforce an explicit filename character and extension policy. 4. Resolve both the output directory and proposed destination, then verify containment with `os.path.commonpath()`. 5. Create the output directory with known permissions and reject symbolic links in the destination path. 6. Avoid silent overwrite by opening new files in exclusive creation mode (`"xb"`) or by requiring explicit confirmation before replacement. 7. Generate a local filename independently and treat the remote filename only as display metadata. 8. Validate downloaded content type and size before saving it. A containment check should follow this pattern: ```python output_root = os.path.realpath(final_output_dir) safe_name = os.path.basename(remote_name) if safe_name != remote_name or safe_name in {"", ".", ".."}: raise ValueError("Unsafe output filename") destination = os.path.realpath(os.path.join(output_root, safe_name)) if os.path.commonpath([output_root, destination]) != output_root: raise ValueError("Output path escapes destination directory") ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/excel_api_client.py:54
Finding
Arbitrary File Truncation Through Unsafe Log Path Handling<![CDATA[ ## Vulnerability Details **File Location**: `scripts/excel_api_client.py`, lines 54-64, 307-315, and 524-525 **Vulnerability Type**: Unsafe temporary file and arbitrary file truncation **Risk Level**: Medium ### Vulnerable Code ```python def _init_log_file(session_id: str = "", log_path: str = "") -> str: """Initialize log file for this session.""" global _LOG_FILE if log_path: _LOG_FILE = log_path else: if not session_id: session_id = str(uuid.uuid4()).replace('-', '_') _LOG_FILE = f"/tmp/excel_run_{session_id}.log" # Clear previous run open(_LOG_FILE, "w").close() return _LOG_FILE ``` ```python if log_path: global _LOG_FILE _LOG_FILE = log_path open(_LOG_FILE, "w").close() else: _init_log_file(run_session_id) ``` ```python parser.add_argument("--log-path", default="", help="Path to save progress log (default: /tmp/excel_run_<session>.log)") ``` ### Technical Analysis The caller can supply an arbitrary `--log-path`, and the file is immediately opened in `"w"` mode without path restriction, ownership validation, file-type validation, or symlink protection. Consequently, any existing file writable by the Skill process can be truncated. The default path is also created through a conventional path-based open operation rather than an atomic secure temporary-file API. Where a predictable or attacker-known path is used in a shared `/tmp` directory, another local user may pre-create a symbolic link pointing to a victim file. This behavior is not a legitimate persistence mechanism; it is transient progress logging. No scheduled task or service installation was found. Nevertheless, the implementation grants the logging feature unnecessary filesystem reach. ### Attack Path **Caller-controlled path scenario:** 1. An attacker influences the CLI arguments and supplies `--log-path` pointing to a sensitive writable file. 2. Task initialization calls `open(_L ...[truncated 822 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create default logs atomically with `tempfile.mkstemp()` or `NamedTemporaryFile()` and restrictive permissions. 2. Store logs in a dedicated private directory owned by the current user rather than a shared `/tmp` namespace. 3. If custom log paths are required, restrict them to an approved logging directory and verify containment after canonicalization. 4. Reject symbolic links and non-regular files. Where supported, use `O_NOFOLLOW`. 5. Avoid truncating existing files. Use exclusive creation with `O_CREAT | O_EXCL`. 6. Set permissions explicitly, such as mode `0600`, because logs may contain filenames, session identifiers, backend progress, and download URLs. 7. Do not suppress all logging exceptions; report secure file-creation failures without exposing credentials. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
references/apikey-fetch.md:73
Finding
API Key Exposed by Verification Instructions<![CDATA[ ## Vulnerability Details **File Location**: `references/apikey-fetch.md`, lines 73-77 **Vulnerability Type**: Plaintext credential exposure **Risk Level**: Low ### Vulnerable Code ```bash # Check that the environment variable is available echo "$SKYWORK_API_KEY" ``` ### Technical Analysis The setup guide instructs users to print the complete API key to the terminal. Terminal output may be retained in scrollback, command-session recordings, CI logs, support captures, screen-sharing sessions, or agent execution logs. Displaying the secret is unnecessary to determine whether the variable is configured. A boolean presence check provides the required diagnostic result without revealing its value. ### Attack Path 1. A user follows the documented verification procedure. 2. The shell expands `SKYWORK_API_KEY` and prints the complete credential. 3. Terminal output is observed, recorded, copied into a support transcript, or collected by an automation system. 4. A party with access to that output recovers and uses the API key. ### Impact Assessment Disclosure allows unauthorized use of the Skywork API to the extent permitted by the exposed key. Potential effects include quota consumption, billing abuse, and access to account-scoped functionality or data. Exploitation depends on another party obtaining the terminal or log output. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions Replace the command with a non-secret presence check: ```bash if [ -n "${SKYWORK_API_KEY:-}" ]; then echo "SKYWORK_API_KEY is set" else echo "SKYWORK_API_KEY is not set" fi ``` Additionally: 1. Warn users not to paste API keys into chat messages, tickets, screenshots, or logs. 2. Recommend secret-management facilities rather than plaintext shell startup files where available. 3. Ensure application errors never include the credential or complete authorization header. 4. Advise immediate key rotation if the secret has been printed into a retained or shared log. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Vague Triggers

High
Confidence
96% confidence
Finding
The upload-based activation rule is ambiguous because it suggests triggering on Excel/CSV/PDF/Image uploads without clearly constraining file purpose, sensitivity, or user consent. That ambiguity can lead to automatic handling of uploaded documents by a remote service when the user may only want local assistance, expanding exposure of potentially confidential content.

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill explicitly instructs passing user queries as-is and sending file paths to a backend that will read the files, but it does not present a clear user-facing warning that uploaded files and prompts are transmitted to an external service. This undermines informed consent and can expose sensitive business, financial, or personal documents to a third party without adequate disclosure.

Agent Config Directory Access

High
Category
Agent Snooping
Content
**Option B — Claude Code settings**

Add the variable to `~/.claude/settings.json`:

```json
{
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
**Option B — Claude Code settings**

Add the variable to `~/.claude/settings.json`:

```json
{
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Agent Config Directory Access

High
Category
Agent Snooping
Content
**Option B — Claude Code settings**

Add the variable to `~/.claude/settings.json`:

```json
{
Confidence
90% confidence
Finding
Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a Python client that uses network access, writes logs and output files, and consumes an API key, but it declares no explicit tool scope or allowed-tools policy. That makes the effective privileges implicit and broad, increasing the chance of unintended execution with file, network, and secret access beyond what users or the platform may expect.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The upload-based activation rule is ambiguous because it suggests triggering on Excel/CSV/PDF/Image uploads without clearly constraining file purpose, sensitivity, or user consent. That ambiguity can lead to automatic handling of uploaded documents by a remote service when the user may only want local assistance, expanding exposure of potentially confidential content.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The documentation sets `zh-CN` as the default language, which imposes a locale choice unless the operator actively changes it. This can violate language/locale policy because the skill does not make neutral language selection the default or require user opt-in before choosing Chinese.

Session Persistence

Medium
Category
Rogue Agent
Content
- Log in with your Skywork account
- Open account / Settings / API Key (**https://skywork.ai/?openApiKeySetting=1**)
- Create or copy your **API key**

If your organization uses a separate console or test environment, use the URL and credentials your team provides.
Confidence
80% confidence
Finding
The guide encourages creating/copying an API key and then storing it in persistent runtime configuration, which establishes long-lived credential presence on disk and across sessions. Session persistence of secrets is not inherently malicious, but without minimization or expiration guidance it increases the window for theft from local compromise, backups, or accidental sharing.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The guide explicitly tells users to place a live API key into persistent configuration and to print it with `echo`, but it gives no warning about shell history, screen sharing, terminal logging, or accidental disclosure in config files. While this is common operational guidance, omitting secret-handling precautions increases the chance of credential exposure and downstream unauthorized API use.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
if not client.health_check():
        raise RuntimeError("Service not available or api key invalid")

    file_ids = [client.upload_file("data.xlsx")]
    outputs = client.run_agent("Create a summary report", file_ids=file_ids)

    for f in outputs:
Confidence
55% confidence
Finding
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
file_ids = []
        total = len(file_paths)
        for i, file_path in enumerate(file_paths):
            file_id = self.upload_file(file_path)
            file_ids.append(file_id)
            # Add delay between uploads (except after the last one)
            if i < total - 1 and delay_between > 0:
Confidence
88% confidence
Finding
The client is designed to upload arbitrary local files to a remote backend service, creating a real data-exfiltration path if users or higher-level agents pass sensitive files without clear consent. In this skill context, the risk is elevated because the skill explicitly triggers on Excel/CSV/PDF/Image uploads and encourages passing the user's original query directly, making accidental transmission of confidential business data more likely.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The client sets the agent request language to "zh-CN" by default, which imposes a specific locale unless the caller explicitly overrides it. This is a natural-language policy concern because the skill does not first ask the user for language preference or make the default locale clearly justified as region-specific.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The command-line interface assigns "zh-CN" as the default for --lang/--language, so users who do not notice the option will have interactions forced into Chinese. This violates the language/locale policy criteria because no opt-in or preference selection is required.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
write_log(f"   - {file_path}")
        for file_path in args.files:
            try:
                file_id = client.upload_file(file_path)
                file_ids.append(file_id)
                write_log(f"   ✅ {os.path.basename(file_path)} -> file_id={file_id}")
            except Exception as e:
Confidence
90% confidence
Finding
The CLI path uploads user-supplied local files directly to the backend, again creating a concrete exfiltration channel for spreadsheet and document contents. In an agent skill intended for data analysis and file conversion, this is particularly relevant because users may provide highly sensitive financial, operational, or personal datasets under the assumption of local-only processing.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The primary command example uses `--language zh-CN`, which may steer implementations toward Chinese output even when the user has not requested that locale. Although the text says to match the user's language, the example still normalizes one specific language choice.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The documentation says to use heartbeat lines only as an internal liveness signal and explicitly says not to output raw heartbeat lines to the user. However, the required monitor command includes `grep -E "\[HEARTBEAT\]" "$EXCEL_LOG" | tail -1`, which prints a raw heartbeat line as part of the command output, directly conflicting with that instruction.

Static analysis

No suspicious patterns detected.