Back to skill

Security audit

huawei-cloud-officeace-expert-migration

Security checks for vulnerabilities and agentic risk

Overview

ClawScan rates this migration skill suspicious because scanners report risks involving file and account boundaries, installation scope, and persistent changes; one part of the analysis was incomplete.

Consider not installing until the reported package-boundary, overwrite, account-isolation, and confirmation issues have been reviewed. The scan covered 21 of 22 components, with partial inspection of SKILL.md, so the evidence is incomplete.

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

T09 · Insecure Skill Coding Practices

Warning
Location
.
Finding

Package-controlled source paths escape the selected package

Content
View full analysis

Impact

Readable external text can be incorporated into generated persona/Skill content, and linked external resources can be copied into staging or installed Skill directories. This exposes unrelated local content to application and agent contexts that did not require it.

T09 · Insecure Skill Coding Practices

Error
Location
.
Finding

Persona installation bypasses overwrite protection and can replace an unrelated trusted Skill

Content
View full analysis

Impact

The package can replace an existing trusted SKILL.md in the data and application stores, changing future agent behavior under that Skill's existing identity. Matching expert cards and user-expert entries can likewise receive the attacker's persona while retaining existing IDs. The overwritten persona Skill and expert card are not backed up on these write paths.

T09 · Insecure Skill Coding Practices

Warning
Location
.
Finding

Account fallback can install package-controlled personas into another account

Content
View full analysis

Impact

Package-controlled experts, personas, skills, and teams can be registered in another account; matching existing registrations can be replaced. Avatar changes can affect matching teams belonging to other accounts. This is an application-account isolation failure, not an operating-system privilege escalation.

Vulnerability Patterns
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (28)

Incomplete referenced artifact analysis

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 284)May include surrounding context.

md
11. **资源引用可解析**:`SKILL.md` 点名的 `references/`、`scripts/`、`assets/`、`templates/` 文件必须真实存在——

Incomplete referenced artifact analysis

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 380)May include surrounding context.

md
| **数字和源端对不上(团队口径漂移)** | 成员没调用官方脚本,自己重写了计算器;`SKILL.md` 里还是 `python ~/.office-claw/skills/huawei-cloud-officeace-expert-migration/scripts/x.py | 升到 1.2.0 重跑(绝对路径

Incomplete referenced artifact analysis

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 430)May include surrounding context.

md
只校验注册层,**不检查技能目录内的资源引用**,所以资源丢光时静态验收仍报「通过(41 项)」。现新增第 11 项检查「资源引用可解析」:解析各专家技能 `SKILL.md` 里的 `references/`、`scripts/`、`assets/`、`templates/` 引用,逐个核对是否落盘,缺失即判失败并

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, file_read, file_write, network, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

The acceptance criteria require Chinese display names without offering a language or locale choice.

Medium
Category
Output Handling
Confidence
90% confidence
Finding

F9 requires team task cards to display Chinese names as a mandatory acceptance condition. This imposes a specific output language without user opt-in or a documented locale-specific justification; requiring readable names rather than internal IDs does not itself require Chinese.

Content

No source excerpt is available for this finding.

The documented runtime-configuration validation is never performed.

Medium
Category
Excessive Agency
Confidence
98% confidence
Finding

L009 explicitly claims the script checks that variant runtime settings have not reverted to placeholder values. However, L101–L147 only check expert visibility and team member names/persona lengths; the script can return success without inspecting any runtime configuration, falsely presenting the documented validation as complete.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/migrate.py (reported line 328)May include surrounding context.

python
def runtime_check(timeout: int = 60) -> tuple[bool | None, str]:
    """应用侧只读确认。返回 (是否通过 | None=跳过, 摘要)。"""
    try:
        proc = subprocess.run(
            [sys.executable, str(HERE / "check_runtime.py")],
            capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout,
        )

Directory-scoped bulk migration silently expands to all discovered source packages when the directory contains no recognized packages.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

When a specified directory contains no recognized packages, bulk migration falls back to scanning all configured source roots instead of stopping. With --install, this exceeds the user's selected scope and can persistently register unrelated experts and skills; printing the expanded count is not explicit authorization.

Content

No source excerpt is available for this finding.

Tainted flow: 'p' from pathlib.Path.read_text (line 492, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 97)May include surrounding context.

python
def write_json(p: Path, data) -> None:
    p.parent.mkdir(parents=True, exist_ok=True)
    p.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def parse_frontmatter(text: str) -> tuple[dict, str]:

Tainted flow: 'dst' from pathlib.Path.read_text (line 427, file read) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
94% confidence
Finding

Although ordinary resource copying is necessary, copy2 follows source symlinks by default, and the adjacent copytree operation also dereferences links without enforcing package-root containment. A malicious package containing links can therefore copy files readable by the migration process from outside the package into staging and installed skills, potentially exposing local sensitive data.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 434)May include surrounding context.

python
shutil.copytree(child, dst)
                copied.append(child.name + "/")
            else:
                shutil.copy2(child, dst)
                copied.append(child.name)
        if skipped:
            report["dropped"].append({"skill": slug, "files": skipped, "reason": "_meta.json 为源平台发布元数据,OfficeAce 不使用"})

Tainted flow: 'src_img' from pathlib.Path.read_text (line 374, file read) → shutil.copyfile (file write)

Medium
Category
Data Flow
Confidence
97% confidence
Finding

The avatar source can come from a manifest path containing traversal components, and source_avatar does not require its resolved location to remain inside the package. An attacker can select an external readable file with an allowed image extension and have it copied into the application's web-served avatar directory; extension checks and the permissive non-PNG quality check do not prevent disclosure.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 659)May include surrounding context.

python
dst = experts / name
                if write and (not dst.is_file() or dst.stat().st_size != Path(src_img).stat().st_size):
                    experts.mkdir(parents=True, exist_ok=True)
                    shutil.copyfile(src_img, dst)
                return f"/avatars/experts/{name}"
        for ext in AVATAR_EXTS:
            cand = experts / f"{slug}{ext}"

Forced installation deletes existing skill directories without the promised pre-write backup.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

Forced installation removes an existing skill directory with rmtree before copying its replacement, without preserving the promised backup. This can irreversibly destroy user modifications, and a copy failure leaves the original skill missing; backups of JSON registration files do not restore skill contents.

Content

No source excerpt is available for this finding.

Tainted flow: 'dst_json' from pathlib.Path.read_text (line 767, file read) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 769)May include surrounding context.

python
(home / "experts").mkdir(parents=True, exist_ok=True)
    dst_json = home / "experts" / f"{name}.json"
    if dst_json.exists():
        shutil.copy2(dst_json, dst_json.with_suffix(f".json.bak-{ts}"))
    shutil.copy2(staging / "experts" / f"{name}.json", dst_json)
    patched.append(f"experts/{name}.json")

Tainted flow: 'dst_json' from pathlib.Path.read_text (line 767, file read) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 770)May include surrounding context.

python
dst_json = home / "experts" / f"{name}.json"
    if dst_json.exists():
        shutil.copy2(dst_json, dst_json.with_suffix(f".json.bak-{ts}"))
    shutil.copy2(staging / "experts" / f"{name}.json", dst_json)
    patched.append(f"experts/{name}.json")

    patch = read_json(staging / "catalog.patch.json")

Tainted flow: 'uc' from pathlib.Path.read_text (line 809, file read) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 811)May include surrounding context.

python
# 用户目录(breeds + roster)
    uc = Path(args.user_catalog) if args.user_catalog else find_user_catalog(home)
    if uc and uc.exists():
        shutil.copy2(uc, uc.with_name(f"{uc.name}.bak-{ts}"))
        data = read_json(uc)
        breeds = [b for b in data.get("breeds", []) if b.get("name") != name]
        breeds.append(patch["breed"])

Tainted flow: 'ue' from pathlib.Path.read_text (line 896, file read) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/migrate_suite.py (reported line 898)May include surrounding context.

python
# 用户级专家列表 users/<uid>/user-experts.json(UI 专家列表的真正数据源)
    ue = find_user_experts(home)
    if ue:
        shutil.copy2(ue, ue.with_name(f"{ue.name}.bak-{ts}"))
        data = read_json(ue)
        card = read_json(dst_json)  # 用已定版头像的落盘卡片,避免回填 staging 里的旧值
        template = (data.get("experts") or [{}])[0]

The gate requires a Chinese-only confirmation question without offering a language choice.

Medium
Category
Output Handling
Confidence
92% confidence
Finding

The message instructs the agent to output only the supplied Chinese question, rather than simply conveying its meaning. This imposes a specific response language without user opt-in or a documented locale-specific justification.

Content

No source excerpt is available for this finding.

The confirmed marker authorizes unrelated sessions and sources despite documented session isolation.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

Any caller using the same home directory is allowed through while the shared confirmed marker is fresh. The gate does not compare the source, session identifier, or caller identity, so approval for one migration also authorizes unrelated sessions and packages during the ten-minute window.

Content

No source excerpt is available for this finding.

The gate does not mechanically enforce the manifest's mandatory user confirmation.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

The gate verifies possession of a nonce already disclosed to the calling agent and elapsed time, not an actual user response. The same agent can supply the nonce after waiting or configure a zero cooldown, so the claimed mechanical enforcement does not prevent installation without user confirmation.

Content

No source excerpt is available for this finding.

The supposedly consumed one-time nonce remains reusable and can renew approval indefinitely.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

Successful confirmation preserves both the nonce and its original issue timestamp, with no consumed-state or maximum-age rejection. Resubmitting that nonce can refresh confirmedAt even after the approval window expires, extending authorization without another user response until the gate file is replaced.

Content

No source excerpt is available for this finding.

Reverse synchronization does not copy application-generated files into the user directory as documented.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

L012 explicitly describes reverse synchronization as a way to retrieve application-generated artifacts from the mirror. However, reverse mode skips records classified as missing-in-home and selects only_dst rather than only_src for copying: mirror-only files are never copied, while user-only files trigger attempts to copy nonexistent mirror sources. When changed files also exist, the operation can report synced while silently omitting new mirror artifacts.

Content

No source excerpt is available for this finding.

Active-user selection falls back to another user despite documenting the owner ID as the sole authority.

Medium
Category
Excessive Agency
Confidence
98% confidence
Finding

If the authoritative owner's directory cannot be resolved, active_user_dir selects another user directory by modification time without verifying ownership. Migration callers use that result to write expert and team registrations, so a shared data directory or stale owner configuration can cause another user's configuration to be modified and imported personas to appear in the wrong account.

Content

No source excerpt is available for this finding.

Runtime configuration falls back to hard-coded values not verified against local model.json.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

The manifest requires runtime configuration to come from the local model.json, and the module describes locally existing provider/model combinations as a hard requirement. When the catalog is missing, unreadable, malformed, or empty, this branch instead returns hard-coded values without verifying their availability, allowing generated expert configurations to violate that requirement.

Content

No source excerpt is available for this finding.

Unresolved role assignments grant every package skill rather than preserving role-specific bindings.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

When role-specific bindings cannot be resolved, the function assigns every package skill rather than requiring confirmation or retaining only the role's own skill. In this migration workflow, bindings feed executable-script instructions into expert personas, so ambiguous or deliberately crafted package metadata can expose unrelated scripts to a role; the returned fallback label does not enforce review.

Content

No source excerpt is available for this finding.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · tests/run_regression_tests.py (reported line 132)May include surrounding context.

python
def test_cooldown_env_override(self):
        nonce, _ = gate.issue(self.home, "x")
        os_val = __import__("os").environ
        os_val["ESM_GATE_COOLDOWN"] = "0"
        try:
            rc_, _ = self._run(install=True, confirm_local=nonce)