Back to skill

Security audit

skill-graph-for-analogical-reasoning

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but its launcher can fall back to running an unverified local command from PATH.

Review before installing. Use it only on skill directories you intend to index, because graph.json can contain local paths and excerpts from skill files. Avoid running it in environments with untrusted PATH entries or unrelated grap-skill executables until the launcher is changed to fail closed or use only a trusted absolute bundled entrypoint.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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 (1)

T07 · Tool Hijacking and Spoofing

Warning
Location
scripts/run_grap_skill.py:13
Finding

Broad Exception Suppression Enables PATH-Based Tool Hijacking

Content
View full analysis

Vulnerability Details

File Location: scripts/run_grap_skill.py, lines 13-38
Vulnerability Type: Untrusted executable resolution and fail-open exception handling
Risk Level: Medium

Vulnerable Code

python
try:
    from auto_grap_skill.cli import main as local_main
    old_argv = sys.argv[:]
    try:
        sys.argv = ["grap-skill", *sys.argv[1:]]
        return int(local_main() or 0)
    finally:
        sys.argv = old_argv
except Exception:
    pass

module_command = [sys.executable, "-m", "auto_grap_skill", *sys.argv[1:]]
module_probe = subprocess.run(
    [sys.executable, "-c", "import auto_grap_skill"],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
)
if module_probe.returncode == 0:
    return subprocess.call(module_command)

command = shutil.which("grap-skill")
if command:
    return subprocess.call([command, *sys.argv[1:]])

Technical Analysis

The wrapper catches every Exception raised not only while importing the bundled package, but also while executing local_main(). It silently discards the original failure and continues through alternative execution mechanisms.

If the module probe fails, the wrapper resolves grap-skill from the process's PATH using shutil.which() and executes the resulting file without validating its origin, ownership, permissions, or integrity. Consequently, a malicious executable named grap-skill in a higher-priority PATH directory can be mistaken for the legitimate tool.

This is especially problematic because the subprocess used for the module probe does not automatically inherit the in-process sys.path.insert() modification. Therefore, after a runtime exception from the bundled implementation, the probe can fail when the package is not separately installed, even though bundled source was initially found and imported successfully.

No shell interpolation is used, so this is not shell command injection. Th ...[truncated 1723 chars]

Remediation
View remediation

Remediation Suggestions

  1. Catch only import-specific failures around the import statement:

    python
    try:
        from auto_grap_skill.cli import main as local_main
    except ImportError as exc:
        local_main = None
        import_error = exc
    

    Once local_main() starts, allow its runtime errors to propagate or report them and terminate with a nonzero status. Do not reinterpret command failures as evidence that another executable should be selected.

  2. Remove the PATH-based fallback entirely from the bundled skill. Prefer the bundled package, whose location is derived from __file__, and fail closed if it cannot be loaded.

  3. If an external fallback is strictly required, require an explicit, trusted absolute executable path supplied through secure configuration. Validate that the executable:

    • Is an absolute path.
    • Is a regular file and not an unexpected symbolic link.
    • Is not located in a broadly writable directory.
    • Has expected ownership and permissions.
    • Optionally matches a pinned cryptographic digest.
  4. Run the module fallback with an explicit trusted import path rather than relying on ambient interpreter state. For example, construct a controlled environment containing the bundled source in PYTHONPATH, or execute the known bundled entrypoint directly.

  5. Log the original exception and selected execution path. Silent exception suppression obscures both operational failures and exploitation attempts.

  6. Add security tests that place a fake grap-skill executable at the front of PATH, force local_main() to fail, and verify that the fake executable is never invoked.

Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
Use this skill when the user wants to work with a **graph skill** over local `SKILL.md` folders.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill explicitly permits code-backed usage via python3 and describes commands that read from and write to local paths, but it declares no permissions or allowed-tools scope. That creates an authorization gap where a runtime may expose broader shell, file, or environment access than intended, increasing the chance of unintended filesystem access or command execution beyond the narrowly described wrapper.

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/run_grap_skill.py (reported line 27)May include surrounding context.

python
pass

    module_command = [sys.executable, "-m", "auto_grap_skill", *sys.argv[1:]]
    module_probe = subprocess.run(
        [sys.executable, "-c", "import auto_grap_skill"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,

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/run_grap_skill.py (reported line 33)May include surrounding context.

python
stderr=subprocess.DEVNULL,
    )
    if module_probe.returncode == 0:
        return subprocess.call(module_command)

    command = shutil.which("grap-skill")
    if command:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
71% confidence
Finding

This branch resolves grap-skill via shutil.which and then executes the first matching binary from the current PATH. In a hostile or untrusted environment, an attacker who can influence PATH or place a malicious grap-skill earlier in search order could cause arbitrary code execution when this wrapper falls back to the external command.

Content

Scanner excerpt · scripts/run_grap_skill.py (reported line 37)May include surrounding context.

python
command = shutil.which("grap-skill")
    if command:
        return subprocess.call([command, *sys.argv[1:]])

    sys.stderr.write(
        "grap-skill is not available. Use the bundled python-package copy in this skill or install the GitHub Python package for this project first.\n"

Unverifiable Dependency: setuptools has 10 known advisory(ies) (CVE-2013-1633 (Setuptools vulnerable to Man-in-the-middle attacks); CVE-2025-47273 (setuptools has a path traversal vulnerability in PackageIndex.download that lead); CVE-2024-6345 (setuptools vulnerable to Command Injection via package URL) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: wheel has 4 known advisory(ies) (CVE-2026-24049 (Wheel Affected by Arbitrary File Permission Modification via Path Traversal in w); CVE-2022-40898 (pypa/wheel vulnerable to Regular Expression denial of service (ReDoS)); CVE-2022-40898 (An issue discovered in Python Packaging Authority (PyPA) Wheel 0.37.1 and earlie) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: PyYAML has 8 known advisory(ies) (CVE-2019-20477 (Deserialization of Untrusted Data in PyYAML); CVE-2020-1747 (Improper Input Validation in PyYAML); CVE-2020-14343 (Improper Input Validation in PyYAML) +5 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The _discover_skills method recursively traverses source paths with rglob and reads every matching SKILL.md/skill.md file, which can touch a large portion of the filesystem under the provided roots. In this code file there is no visible warning, logging, or explanatory docstring disclosing that broad file access occurs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The save method creates directories and writes a graph.json file to disk, but the code includes no confirmation prompt, print/log disclosure, or inline warning describing this side effect. For a code-file audit, file writes are safety-relevant operations that should have some form of user disclosure unless clearly surfaced elsewhere in the file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.