Back to skill

Security audit

OSS Artifact Upload

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it can upload local files to cloud storage too easily and has a directory-upload flaw that could include unintended files.

Install only if you are comfortable with the agent uploading selected local artifacts to your configured OSS bucket and returning signed download links. Use narrowly scoped OSS credentials, avoid sensitive directories, confirm exact file paths before upload, and be aware that directory uploads may follow symlinks and leave temporary ZIP copies behind.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/upload_to_oss.py:62
Finding
Directory Uploads Follow Symbolic Links Outside the Selected Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/upload_to_oss.py`, lines 62–77 **Vulnerability Type**: Symbolic-link traversal during archive creation **Risk Level**: Medium ### Vulnerable Code ```python def iter_files(path: Path) -> Iterable[Path]: for item in sorted(path.rglob("*")): if item.is_file(): yield item def make_archive(paths: list[Path]) -> Path: timestamp = dt.datetime.now(dt.UTC).strftime("%Y%m%dT%H%M%SZ") archive = Path(tempfile.gettempdir()) / f"openclaw-artifact-{timestamp}-{uuid.uuid4().hex[:8]}.zip" with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED) as zf: for source in paths: if source.is_dir(): base = source.parent for file_path in iter_files(source): zf.write(file_path, file_path.relative_to(base).as_posix()) else: zf.write(source, source.name) return archive ``` ### Technical Analysis `Path.is_file()` follows symbolic links by default. Consequently, a symbolic link encountered by `path.rglob("*")` is treated as a regular file when its target is a file. The subsequent `ZipFile.write()` operation opens the symbolic-link target and stores its contents in the archive. The implementation does not reject symbolic links and does not resolve each candidate path and verify that the resolved target remains within the user-selected directory. A directory prepared by an untrusted party can therefore include links to files outside the intended upload boundary. This does not grant access to files that the running process cannot already read. However, it can cause readable local data outside the selected artifact directory to be unintentionally packaged and transmitted to OSS. ### Attack Path 1. An attacker gains the ability to influence the contents of a directory that will be uploaded. 2. The attacker creates a symbolic link inside that directory, for example: ```text ...[truncated 1013 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject symbolic links while enumerating directory contents: ```python def iter_files(path: Path) -> Iterable[Path]: for item in sorted(path.rglob("*")): if item.is_symlink(): raise SystemExit(f"Symbolic links are not allowed: {item}") if item.is_file(): yield item ``` 2. Apply a containment check in addition to rejecting links: ```python root = source.resolve() candidate = file_path.resolve(strict=True) if not candidate.is_relative_to(root): raise SystemExit(f"Path escapes upload directory: {file_path}") ``` 3. Perform the containment check immediately before opening each file to reduce time-of-check/time-of-use exposure. 4. If symbolic links must be supported, archive the link metadata rather than dereferencing the target, and clearly document that behavior. 5. Add regression tests covering links to: - Files outside the selected directory. - Files within the selected directory. - Broken links. - Chained symbolic links. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/upload_to_oss.py:68
Finding
Generated Temporary Archives Are Not Deleted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/upload_to_oss.py`, lines 68–79 and 155–181 **Vulnerability Type**: Persistent sensitive data in temporary storage **Risk Level**: Low ### Vulnerable Code The archive is created under the system temporary directory: ```python def make_archive(paths: list[Path]) -> Path: timestamp = dt.datetime.now(dt.UTC).strftime("%Y%m%dT%H%M%SZ") archive = Path(tempfile.gettempdir()) / f"openclaw-artifact-{timestamp}-{uuid.uuid4().hex[:8]}.zip" with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED) as zf: for source in paths: if source.is_dir(): base = source.parent for file_path in iter_files(source): zf.write(file_path, file_path.relative_to(base).as_posix()) else: zf.write(source, source.name) return archive ``` The main workflow uses the archive but does not remove it after success or failure: ```python def main() -> None: args = parse_args() access_key_id = require(env("OSS_ACCESS_KEY_ID"), "OSS_ACCESS_KEY_ID") access_key_secret = require(env("OSS_ACCESS_KEY_SECRET"), "OSS_ACCESS_KEY_SECRET") bucket = require(args.bucket, "OSS_BUCKET") endpoint = require(args.endpoint, "OSS_ENDPOINT") public_endpoint = require(args.public_endpoint, "OSS_PUBLIC_ENDPOINT or OSS_ENDPOINT") security_token = env("OSS_STS_TOKEN") source = resolve_source(args.paths) original_paths = [Path(p).expanduser().resolve() for p in args.paths] if args.object_key and (len(original_paths) != 1 or original_paths[0].is_dir()): raise SystemExit("--object-key is only supported for a single file upload") key = args.object_key or default_object_key(args.prefix, source) upload_file(source, bucket, key, endpoint, access_key_id, access_key_secret, security_token) download_url, expires_epoch = build_signed_get_url( public_endpoint, bucket, ...[truncated 2652 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Track whether `source` is a generated archive and remove it in a `finally` block: ```python source = resolve_source(args.paths) generated_archive = not ( len(args.paths) == 1 and Path(args.paths[0]).expanduser().resolve().is_file() ) try: upload_file(source, bucket, key, endpoint, access_key_id, access_key_secret, security_token) # Generate and print the signed URL. finally: if generated_archive: source.unlink(missing_ok=True) ``` 2. Prefer `tempfile.TemporaryDirectory()` so the archive and its containing directory are automatically removed. 3. Create temporary storage with owner-only permissions and ensure the archive is not exposed through a permissive process umask. 4. Clean up archives on every path, including: - Successful upload and URL generation. - Upload failure. - Signing failure. - Output serialization failure. - User interruption where practical. 5. Avoid returning a deleted temporary archive path as the result's `source`, or explicitly label it as a transient path that has already been removed. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:19
Finding
OSS SDK Installation Is Not Version-Pinned<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 19–21 and 45 **Vulnerability Type**: Unpinned third-party dependency **Risk Level**: Low ### Vulnerable Configuration and Instructions The installation metadata identifies the package without a version constraint: ```yaml - kind: uv package: oss2 label: Install Alibaba Cloud OSS Python SDK ``` The documentation also instructs users to install the latest available release: ```text The uploader uses the Alibaba Cloud `oss2` Python SDK. If it is not installed, install it with `python3 -m pip install oss2`. ``` ### Technical Analysis The Skill depends on the third-party `oss2` package but does not pin a reviewed version or provide package integrity hashes. As a result, installations performed at different times may retrieve different package releases whose behavior was not covered by this audit. The uploader imports `oss2` in-process and gives it access to the current Python process, environment, filesystem privileges, and OSS credentials. A compromised upstream release, package-index account, dependency of `oss2`, or package source could therefore execute code under the Agent's privileges. No evidence was found that the currently referenced package is malicious. This finding concerns the absence of dependency reproducibility and integrity controls rather than a confirmed malicious package. ### Attack Path 1. The `oss2` dependency is unavailable in the runtime environment. 2. A user or automated installer follows the Skill's instruction and runs: ```bash python3 -m pip install oss2 ``` 3. The installer resolves the latest package version and its transitive dependencies from the configured package index. 4. If an upstream release or dependency has been compromised, malicious code is installed. 5. The uploader imports the installed module. 6. The malicious module executes with the uploader's process privileges and may access OSS credentials from environment variables. ### ...[truncated 613 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `oss2` to a reviewed, exact version in installation metadata and documentation: ```yaml package: oss2==<reviewed-version> ``` 2. Maintain a lockfile that also pins all transitive dependencies. 3. Use package hashes and require hash verification during installation: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` 4. Install packages only from an explicitly trusted package index over TLS. 5. Periodically review and update the pinned version through a controlled dependency-update process that includes: - Vulnerability scanning. - Release-note review. - Integrity verification. - Functional and security regression testing. 6. Run the uploader with narrowly scoped OSS credentials and minimal filesystem privileges to reduce the impact of any dependency compromise. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Vague Triggers

High
Confidence
98% confidence
Finding
Enabling implicit invocation allows the skill to activate without an explicit user request, and the skill’s function is to send local workspace artifacts to Alibaba Cloud OSS using environment-provided credentials. Because the capability performs outbound data transfer and generates shareable signed links, weak activation boundaries materially increase the risk of unauthorized publication or exfiltration of confidential artifacts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill uses sensitive environment-provided credentials and can perform remote uploads, but it declares no explicit tool scope or permission boundary. That increases the chance an agent can invoke it without clear policy gating or user awareness, leading to unintended disclosure of local artifacts to the configured OSS bucket.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger text is very broad: requests to 'upload, share, publish, or return downloadable links' for many artifact types could match ordinary user intents that do not imply consent to external exfiltration. In this context, broad auto-invocation is risky because the skill transmits local workspace content to a remote cloud bucket using preconfigured credentials.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill does not prominently warn that local files will be uploaded to a remote Alibaba Cloud bucket using environment-sourced credentials and then exposed via a signed download URL. Users may believe they are only preparing a local artifact, while the skill actually performs external transfer and link generation, creating a clear risk of unintended data disclosure.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The default prompt broadly instructs the agent to invoke this upload skill whenever artifacts should be uploaded and shared, but it does not define strict user-consent or eligibility boundaries. In combination with a skill that exfiltrates workspace artifacts to external cloud storage and returns signed links, this can cause over-triggering and unintended disclosure of sensitive generated files.

Static analysis

No suspicious patterns detected.