Back to skill

Security audit

cad-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent CAD automation tool, but it gives callers too much control over executable paths, configuration files, and forced process termination.

Review carefully before installing. Use it only in a trusted local workstation context, avoid passing custom config_file values, verify any saved executable path yourself, and do not use close_app unless you are prepared for forced closure and possible unsaved-work loss.

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
skill_runner.py:35
Finding

Caller-Controlled Configuration Enables Arbitrary Local Program Execution and Process Termination

Content
View full analysis

Vulnerability Details

File Location: skill_runner.py:35-83; cad_launcher.py:58-73, 257-268, 327-359, 433-499
Vulnerability Type: Caller-controlled security-sensitive configuration
Risk Level: High

Vulnerable Code

python
if skill == "launch_app":
    result = launch_app(
        app=args.get("app", ""),
        config_file=args.get("config_file", "config.json")
    )
python
elif skill == "close_app":
    result = close_app(
        app=args.get("app", ""),
        config_file=args.get("config_file", "config.json"),
        force=args.get("force", True)
    )
python
def load_config(config_file: str = CONFIG_FILE) -> Dict[str, Any]:
    if not os.path.isfile(config_file):
        return fail(f"配置文件不存在: {config_file}")

    try:
        with open(config_file, "r", encoding="utf-8") as f:
            cfg = json.load(f)
        return ok(config=cfg)
    except Exception as e:
        return fail(f"读取配置文件失败: {e}")
python
def find_executable(app: str, config: Dict[str, Any]) -> Optional[str]:
    app_info = get_app_info(app, config)
    if not app_info:
        return None

    for p in app_info.get("saved_paths", []):
        if os.path.isfile(p):
            return p

    for p in app_info.get("candidate_paths", []):
        if os.path.isfile(p):
            return p

    return None
python
def launch_app(app: str, config_file: str = CONFIG_FILE) -> Dict[str, Any]:
    app_check = validate_app(app)
    if not app_check["success"]:
        return app_check
    app = app_check["app"]

    cfg = load_config(config_file)
    if not cfg["success"]:
        return cfg

    config = cfg["config"]
    exe_path = find_executable(app, config)

    if not exe_path:
        detect_result = detect_app_path(app, config_file=config_file)
        if detect_result["success"]:
            exe_path = detect_result
...[truncated 3234 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove config_file from caller-controlled skill arguments.
  • Resolve the configuration from a fixed, skill-owned path derived from __file__, rather than the current working directory.
  • If configuration selection is required, canonicalize it with os.path.realpath() and verify that it remains inside an approved directory.
  • Reject absolute paths, traversal components, network paths, and reparse-point escapes.
  • Treat packaged candidate paths and process-name allowlists as immutable application metadata.
  • Validate executable paths against application-specific expected filenames and approved installation roots.
  • Validate process names against a hardcoded mapping rather than values from a selectable JSON file.
  • Run the skill under a restricted operating-system account and prevent it from terminating privileged or unrelated processes.
  • Add security tests proving that external configuration files cannot select arbitrary executable paths or process names.

T09 · Insecure Skill Coding Practices

Error
Location
cad_launcher.py:218
Finding

set_app_path Converts the CAD Interface into an Arbitrary Executable Launcher

Content
View full analysis

Vulnerability Details

File Location: cad_launcher.py:218-253, 257-268, 327-359
Vulnerability Type: Insufficient validation of executable paths
Risk Level: High

Vulnerable Code

python
def set_app_path(app: str, path: str, config_file: str = CONFIG_FILE) -> Dict[str, Any]:
    app_check = validate_app(app)
    if not app_check["success"]:
        return app_check
    app = app_check["app"]

    if not path:
        return fail("未提供 path 参数", app=app)

    path = path.strip()

    if not os.path.isfile(path):
        return fail("提供的路径不存在或不是文件", app=app, path=path)

    cfg = load_config(config_file)
    if not cfg["success"]:
        return cfg
    config = cfg["config"]

    app_info = get_app_info(app, config)
    if not app_info:
        return fail(f"配置中未找到软件: {app}", app=app)

    saved_paths = app_info.setdefault("saved_paths", [])

    if path not in saved_paths:
        saved_paths.insert(0, path)

    save_result = save_config(config, config_file=config_file)
    if not save_result["success"]:
        return save_result

    return ok(
        app=app,
        path=path,
        message=f"已保存 {app} 路径"
    )
python
def find_executable(app: str, config: Dict[str, Any]) -> Optional[str]:
    app_info = get_app_info(app, config)
    if not app_info:
        return None

    for p in app_info.get("saved_paths", []):
        if os.path.isfile(p):
            return p

    for p in app_info.get("candidate_paths", []):
        if os.path.isfile(p):
            return p

    return None
python
try:
    subprocess.Popen([exe_path], shell=False)
    return ok(
        message=f"{app} 已启动",
        app=app,
        exe_path=exe_path
    )
except Exception as e:
    return fail(
        f"{app} 启动失败: {e}",
        app=app,
        exe_path=exe_path
    )

Technical Analysis

set_app_path() ...[truncated 1956 chars]

Remediation
View remediation

Remediation Suggestions

  • Define an application-specific allowlist of expected executable basenames, such as the documented vendor executable for each supported product.
  • Canonicalize the supplied path before validation and reject relative paths, network paths, links, and reparse points that escape approved roots.
  • Restrict executable locations to approved installation directories such as administrator-controlled program directories.
  • Verify Windows Authenticode signatures and expected publishers where practical.
  • Confirm that the file is a Windows executable rather than relying solely on os.path.isfile().
  • Require explicit, informed user confirmation before persisting and launching a newly supplied executable.
  • Store configuration in a location writable only by the intended user or administrator.
  • Record configuration changes in an audit log and provide a way to review and revoke saved paths.
  • Revalidate the executable immediately before every launch to reduce path-replacement and time-of-check/time-of-use risks.

T09 · Insecure Skill Coding Practices

Warning
Location
cad_launcher.py:433
Finding

CAD Applications Are Forcibly Terminated by Default

Content
View full analysis

Vulnerability Details

File Location: cad_launcher.py:433-499; skill_runner.py:56-60
Vulnerability Type: Unsafe destructive default
Risk Level: Medium

Vulnerable Code

python
def close_app(app: str, config_file: str = CONFIG_FILE, force: bool = True) -> Dict[str, Any]:
    app_check = validate_app(app)
    if not app_check["success"]:
        return app_check
    app = app_check["app"]

    cfg = load_config(config_file)
    if not cfg["success"]:
        return cfg

    config = cfg["config"]
    app_info = get_app_info(app, config)
    if not app_info:
        return fail(f"配置中未找到软件: {app}", app=app)

    process_names = app_info.get("process_names", [])
    if not process_names:
        return fail(f"{app} 未配置 process_names", app=app)

    status = is_app_running(app, config_file=config_file)
    if not status["success"]:
        return status

    if not status["running"]:
        return ok(
            message=f"{app} 当前未运行,无需关闭",
            app=app,
            closed=False
        )

    errors = []
    closed_any = False

    for proc in process_names:
        cmd = ["taskkill", "/IM", proc]
        if force:
            cmd.append("/F")

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                encoding="gbk",
                errors="ignore"
            )
            if result.returncode == 0:
                closed_any = True
            else:
                msg = result.stderr.strip() or result.stdout.strip()
                if msg:
                    errors.append(msg)
        except Exception as e:
            errors.append(str(e))

    if closed_any:
        return ok(
            message=f"{app} 已关闭",
            app=app,
            closed=True,
            errors=errors
        )

    return fail(
        f"
...[truncated 1849 chars]
Remediation
View remediation

Remediation Suggestions

  • Change the default to force=False in both close_app() and skill_runner.py.
  • Attempt graceful application shutdown before using process termination.
  • Require explicit user confirmation before any forced termination.
  • If graceful shutdown times out, report that condition and request authorization before escalating to /F.
  • Warn that forced closure may discard unsaved work and identify the processes that will be affected.
  • After issuing a close request, verify the process state rather than assuming a successful command means all application state was safely closed.
  • Consider product-specific automation APIs for clean shutdown where available.

other

Note
Location
cad_launcher.py:503
Finding

get_active_app Discloses the Complete Foreground Window Title

Content
View full analysis

Vulnerability Details

File Location: cad_launcher.py:503-524
Vulnerability Type: Sensitive window metadata disclosure
Risk Level: Low

Vulnerable Code

python
def get_active_app(config_file: str = CONFIG_FILE) -> Dict[str, Any]:
    cfg = load_config(config_file)
    if not cfg["success"]:
        return cfg

    try:
        title = _get_active_window_title()
    except Exception as e:
        return fail(f"获取当前活动窗口失败: {e}")

    active_app = _detect_app_from_window_title(title)

    running_result = get_running_apps(config_file=config_file)
    if not running_result["success"]:
        return running_result

    return ok(
        active_app=active_app,
        active_window_title=title,
        detected=active_app is not None,
        running_apps=running_result["running_apps"],
        message="检测完成"
    )

Technical Analysis

The foreground window title is required internally to classify the active application, but the complete title is also returned to the caller through active_window_title.

Window titles can contain document filenames, project identifiers, customer names, directory information, browser page titles, communication subjects, or data from unrelated applications. Returning the raw title exceeds what is necessary to answer which supported CAD application is active.

Attack Path

  1. A sensitive document or unrelated application is focused on the workstation.
  2. Its title contains a filename, project name, customer name, or other sensitive metadata.
  3. A caller invokes get_active_app.
  4. _get_active_window_title() reads the complete foreground title.
  5. The response returns that title even if no supported CAD application is detected.
  6. Repeated calls can reveal a sequence of user activity and document metadata.

Impact Assessment

The caller may learn sensitive local activity and document metadata that is not required for CAD ...[truncated 310 chars]

Remediation
View remediation

Remediation Suggestions

  • Return only active_app, detected, and the supported running-application list by default.
  • Do not return a raw window title when no supported CAD application is detected.
  • If diagnostic access to titles is necessary, place it behind a separate, explicitly authorized option.
  • Redact filenames, paths, and other variable document metadata before returning diagnostic titles.
  • Document the privacy implications and avoid retaining or logging raw titles.
  • Add tests confirming that the standard action does not expose titles from unrelated applications.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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 (10)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill describes capabilities that imply shell execution plus file read/write behavior, but it declares no explicit tool scope or permissions boundary. That creates an authorization ambiguity: an agent or reviewer cannot reliably determine what operations are intended or restrict usage to the minimum necessary, increasing the chance of unsafe command execution or unintended file access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill supports closing applications, including a force option, without requiring a user-facing warning or confirmation about unsaved work. In a CAD context this is especially risky because users often have large unsaved models or in-progress edits, so forced termination can directly cause data loss and workflow disruption.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The file returns and prints user-facing natural-language messages entirely in Chinese, such as error and status strings, without offering any language selection or documenting a justified locale restriction. This can violate language/locale policy when users have not opted into that language.

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 · cad_launcher.py (reported line 121)May include surrounding context.

python
def _tasklist_output() -> str:
    try:
        return subprocess.check_output(
            ["tasklist"],
            text=True,
            encoding="gbk",

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 · cad_launcher.py (reported line 128)May include surrounding context.

python
errors="ignore"
        ).lower()
    except Exception:
        return subprocess.check_output(
            ["tasklist"],
            text=True,
            encoding="utf-8",

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
95% confidence
Finding

The code launches an executable path taken from configuration, including user-saved paths, with only an existence check and no trust validation, signature verification, path allowlisting, or filename/type constraint. In this skill's context, that means a user or upstream agent can persist and later execute any local binary, effectively turning the skill into a local arbitrary program launcher.

Content

Scanner excerpt · cad_launcher.py (reported line 348)May include surrounding context.

python
return detect_result

    try:
        subprocess.Popen([exe_path], shell=False)
        return ok(
            message=f"{app} 已启动",
            app=app,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

The function executes [exe_path, file_path] where exe_path can come from untrusted configuration and file_path is user-controlled. Although shell=False avoids shell injection, this still enables arbitrary binary execution and passing attacker-chosen files/arguments to that binary, which is dangerous in a skill specifically designed to open local files in privileged desktop applications.

Content

Scanner excerpt · cad_launcher.py (reported line 415)May include surrounding context.

python
time.sleep(wait_seconds)

        subprocess.Popen([exe_path, file_path], shell=False)

        return ok(
            message=f"已使用 {app} 打开文件",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

close_app defaults to force=True and can kill CAD processes without any confirmation, save-state check, or user-facing warning. In a CAD skill, this is especially risky because these applications commonly hold unsaved design work, so force-closing can cause immediate data loss and workflow disruption even if no attacker is present.

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 · cad_launcher.py (reported line 472)May include surrounding context.

python
cmd.append("/F")

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The fallback error message string 未知 skill is hard-coded in Chinese, which imposes a specific language on users without any indication of locale selection or opt-in. This matches the language/locale policy category because the file provides user-visible natural-language output in only one language.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.