Back to skill

Security audit

MerchantGuard

Security checks for vulnerabilities and agentic risk

Overview

MerchantGuard appears purpose-built for agent security and compliance, but users should review it because its install and scan boundaries are under-scoped in ways that can affect local files and supply-chain trust.

Install only if you trust MerchantGuard and are comfortable sending agent identifiers, endpoints, compliance questions, merchant metrics, and optional wallet addresses to its API. Avoid running the scanner on directories containing untrusted symlinks, avoid broad scans like your whole home or skills directory unless needed, and prefer pinned, reviewed package versions instead of the unversioned npx examples.

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

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
guard.py:116
Finding
Scan Directory Boundary Bypass Through Symbolic Links<![CDATA[ ## Vulnerability Details **File Location**: `guard.py:82-83` and `guard.py:116-122` **Vulnerability Type**: Symbolic-link traversal outside the authorized scan root **Risk Level**: Medium ### Vulnerable Code ```python def scan_file(filepath: Path) -> List[Dict[str, Any]]: """Scan a single file for security patterns.""" findings = [] try: content = filepath.read_text(errors="ignore") except Exception: return findings ``` ```python for filepath in scan_path.rglob("*"): if filepath.is_file() and filepath.suffix in scan_extensions: if any(skip in filepath.parts for skip in skip_dirs): continue findings = scan_file(filepath) # Make paths relative for f in findings: f["file"] = str(filepath.relative_to(scan_path)) all_findings.extend(findings) files_scanned += 1 ``` ### Technical Analysis The scanner recursively identifies files under a user-selected directory using `Path.rglob()`. It then checks candidates with `Path.is_file()` and reads them with `Path.read_text()`. Both operations follow symbolic links. The implementation does not resolve each candidate to its canonical path and verify that the resolved target remains inside `scan_path`. Consequently, a symbolic link located within the selected scan directory can point to a readable file outside that directory, causing the scanner to access data beyond its declared authorization boundary. This behavior contradicts the statement in `SKILL.md` that the Skill does not access files outside the specified scan directory. The behavior also exceeds the minimum filesystem access required for scanning a selected project. The scanner does not print complete file contents and does not upload scan results. However, it can disclose derived information such as matched credential types, finding categories, and line numbers from an external file. The static pre-scan warning concerning SSH keys does not indi ...[truncated 1437 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Resolve each candidate path and enforce containment before reading it. Reject symbolic links unless following them is an explicitly documented feature. For Python 3.9 compatibility, containment can be validated as follows: ```python scan_path = Path(path).resolve() for filepath in scan_path.rglob("*"): try: if filepath.is_symlink(): continue resolved = filepath.resolve(strict=True) resolved.relative_to(scan_path) except (OSError, RuntimeError, ValueError): continue if not resolved.is_file() or resolved.suffix not in scan_extensions: continue findings = scan_file(resolved) ``` Additional hardening should include: 1. Reject both file symlinks and directory symlinks. 2. Perform the containment check immediately before opening the file to reduce time-of-check/time-of-use risk. 3. Consider opening files through a directory file descriptor with platform-specific no-follow protections where strong adversarial filesystem guarantees are required. 4. Add tests covering symlinks to files and directories outside the scan root. 5. Document the precise scan boundary and any intentionally supported link behavior. 6. Return a warning when a candidate is skipped because it resolves outside the authorized root. ]]>

T08 · Insecure Dependencies

Warning
Location
setup.py:11
Finding
Unpinned Remote Dependencies and Immediate Package Execution<![CDATA[ ## Vulnerability Details **File Location**: `setup.py:11-13`, `SKILL.md:137-140`, `SKILL.md:179`, `SKILL.md:187-190`, `README.md:18`, and `README.md:31-33` **Vulnerability Type**: Mutable dependency resolution and unpinned remote package execution **Risk Level**: Medium ### Vulnerable Code and Instructions `setup.py:11-13`: ```python install_requires=[ "requests>=2.28.0", ], ``` Installation and execution instructions in `SKILL.md` and `README.md` include: ```bash pip install requests ``` ```bash npm install @merchantguard/guard npm install @merchantguard/mystery-shopper npm install @merchantguard/guardscan npm install @merchantguard/probe-handler ``` ```bash npx @merchantguard/guardscan . npx @merchantguard/mystery-shopper MyAgent npm install @merchantguard/guard ``` ### Technical Analysis The Python dependency permits any `requests` release equal to or newer than version 2.28.0. The manual pip instruction does not specify a version or package hash. The npm instructions likewise omit exact versions and integrity constraints. Of particular concern, `npx` can download and immediately execute the currently resolved package version. The code executed through these commands is not contained in the audited project and may change after the Skill itself has been reviewed. No evidence was found that the currently named packages are malicious. The risk arises from mutable supply-chain resolution: a compromised maintainer account, registry package, dependency, or distribution channel could cause future installations to retrieve and execute code different from the reviewed version. The manual Skill installation also downloads mutable files over HTTPS from the project's declared domain. Those instructions do not directly pipe downloaded content into a shell, so they are less immediately dangerous, but checksums or signatures are not provided. ### Attack Path 1. An attacker compromises a package maintainer account, package registry entry, transiti ...[truncated 1138 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin dependencies to exact, reviewed versions rather than open-ended ranges: ```python install_requires=[ "requests==2.32.5", ] ``` 2. Use hash-verified Python dependency installation through a locked requirements file: ```text requests==2.32.5 --hash=sha256:<verified-package-hash> ``` Install it with: ```bash python -m pip install --require-hashes -r requirements.txt ``` 3. Pin exact npm package versions in every documented command: ```bash npm install @merchantguard/guard@<reviewed-version> npx --yes @merchantguard/guardscan@<reviewed-version> . ``` 4. Prefer installing reviewed dependencies and using `npm ci` with a committed lockfile instead of allowing `npx` to retrieve mutable packages during execution. 5. Record and verify npm integrity metadata through `package-lock.json`. 6. Review transitive dependencies and package lifecycle scripts before release. 7. Publish signed release artifacts or cryptographic checksums for manually downloaded Skill files. 8. Update installation documentation whenever reviewed versions change, and retain prior signed artifacts for reproducible deployments. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (22)

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Content
m typing import Optional, Dict, Any, List

try:
    import requests
except ImportError:
    print("ERROR: 'requests' package required. Run: pip install requests")
    sys.exit(1)

# ============================================================================
# CONFIG
# ============================================================================

API_BASE = "https://merchantguard.ai/api"
API_KEY = os.environ.get("MERCHANTGUARD_API_KEY", "")
VERSION = "2.0.0"

HEADERS = {
    "Content-Type": "application/json",
    "User-Agent": f"MerchantGuard-OpenClaw/{VERSION}",
}
if API_KEY:
    HEADERS["Authorization"] = f"Bearer {API_KEY}"

# ============================================================================
# LOCAL SCAN PATTERNS (runs 100% locally — no code uploaded)
# ============================================================================

DANGEROUS_PATTERNS = {
    "hardcoded_secrets": [
        (r'(?:api[_-]?key|secret|token|password)\s*[:=]\s*["\'][^"\']{8,}', "Possible hardco
Confidence
85% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
(r'(?:api[_-]?key|secret|token|password)\s*[:=]\s*["\'][^"\']{8,}', "Possible hardcoded secret"),
        (r'AKIA[0-9A-Z]{16}', "AWS Access Key ID"),
        (r'sk[_-]live[_-][a-zA-Z0-9]{24,}', "Stripe Secret Key"),
        (r'ghp_[a-zA-Z0-9]{36}', "GitHub Personal Access Token"),
    ],
    "sensitive_access": [
        (r'\.ssh', "SSH directory access"),
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
],
    "sensitive_access": [
        (r'\.ssh', "SSH directory access"),
        (r'\.env', "Environment file access"),
        (r'private[_-]?key', "Private key reference"),
        (r'id_rsa|id_ed25519', "SSH key file reference"),
        (r'wallet\.dat', "Crypto wallet file access"),
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
(r'private[_-]?key', "Private key reference"),
        (r'id_rsa|id_ed25519', "SSH key file reference"),
        (r'wallet\.dat', "Crypto wallet file access"),
        (r'\.gnupg', "GPG keyring access"),
        (r'keychain', "Keychain access"),
        (r'credentials', "Credentials file access"),
    ],
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
(r'id_rsa|id_ed25519', "SSH key file reference"),
        (r'wallet\.dat', "Crypto wallet file access"),
        (r'\.gnupg', "GPG keyring access"),
        (r'keychain', "Keychain access"),
        (r'credentials', "Credentials file access"),
    ],
    "prompt_injection": [
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
(r'id_rsa|id_ed25519', "SSH key file reference"),
        (r'wallet\.dat', "Crypto wallet file access"),
        (r'\.gnupg', "GPG keyring access"),
        (r'keychain', "Keychain access"),
        (r'credentials', "Credentials file access"),
    ],
    "prompt_injection": [
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
all_findings = []
    files_scanned = 0
    skip_dirs = {".git", "node_modules", "__pycache__", ".next", "dist", "build", ".venv", "venv"}
    scan_extensions = {".py", ".js", ".ts", ".tsx", ".jsx", ".md", ".yaml", ".yml", ".json", ".env", ".sh", ".toml"}

    for filepath in scan_path.rglob("*"):
        if filepath.is_file() and filepath.suffix in scan_extensions:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The installation instructions direct users to download Python code and skill files from a remote domain and install dependencies, but provide no warning about trust, code review, signatures, or integrity verification. This is dangerous because it normalizes executing remotely hosted code in a persistent local skill directory, increasing the chance of supply-chain compromise or silent malicious updates.

Session Persistence

Medium
Category
Rogue Agent
Content
openclaw skill install merchantguard

# Or manual
mkdir -p ~/.openclaw/skills/merchantguard
cd ~/.openclaw/skills/merchantguard
curl -LO https://merchantguard.ai/skills/guard/guard.py
curl -LO https://merchantguard.ai/skills/guard/SKILL.md
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
openclaw skill install merchantguard

# Or manual
mkdir -p ~/.openclaw/skills/merchantguard
cd ~/.openclaw/skills/merchantguard
curl -LO https://merchantguard.ai/skills/guard/guard.py
curl -LO https://merchantguard.ai/skills/guard/SKILL.md
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Skill Enumeration

Medium
Category
Agent Snooping
Content
mkdir -p ~/.openclaw/skills/merchantguard
cd ~/.openclaw/skills/merchantguard
curl -LO https://merchantguard.ai/skills/guard/guard.py
curl -LO https://merchantguard.ai/skills/guard/SKILL.md
curl -LO https://merchantguard.ai/skills/guard/claw.json
pip install requests
```
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
mkdir -p ~/.openclaw/skills/merchantguard
cd ~/.openclaw/skills/merchantguard
curl -LO https://merchantguard.ai/skills/guard/guard.py
curl -LO https://merchantguard.ai/skills/guard/SKILL.md
curl -LO https://merchantguard.ai/skills/guard/claw.json
pip install requests
```
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
The README instructs users to execute an npm package via npx without pinning an exact version, which means the code fetched and run can change over time. If the package is compromised, typo-squatted, or a malicious update is published, users may execute unreviewed code directly from the registry.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
This command runs a remote npm package with npx without specifying a fixed version, creating a supply-chain risk. Future package changes or registry compromise could cause different code to execute than what the user expected at the time the README was reviewed.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document claims GuardScan runs locally and says 'nothing uploaded', but the API section states all commands call MerchantGuard's remote API. Without a clear warning about what data is transmitted for commands like shopper, score, coach, and certify, users may expose agent endpoints, merchant metrics, prompts, or sensitive operational data under a mistaken assumption of local-only processing.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
The skill instructs users to execute an unpinned package via npx, which fetches the latest published version at runtime. If the package is compromised, typosquatted, or a malicious update is published, users could execute attacker-controlled code immediately on their machine.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
This command also invokes an unpinned remote package through npx, creating a supply-chain execution risk. Because the package may contact remote endpoints and process agent targets, compromise of the package could lead to code execution, data exfiltration, or hostile probing from the user's environment.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The docstring for scan_directory says '100% local — nothing uploaded,' which presents the skill as purely local in a way that conflicts with the module's actual capabilities elsewhere: it can send data such as agent identifiers, endpoint URLs, questions, and wallet addresses to merchantguard.ai via API calls. This is more than an incomplete comment because the top-level module branding presents a compliance layer while this function-level documentation makes a stronger locality/safety claim that is not true for the overall skill behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
if method == "GET":
            resp = requests.get(url, headers=HEADERS, timeout=timeout)
        else:
            resp = requests.post(url, headers=HEADERS, json=data or {}, timeout=timeout)
        resp.raise_for_status()
        return resp.json()
    except requests.Timeout:
Confidence
80% confidence
Finding
This code performs external HTTP GET/POST requests to a fixed remote API and can send user-supplied data over the network. Network transmission is expected for this product, but it still represents a real data-flow/security concern because users may submit identifiers, wallet addresses, questions, or endpoint URLs to a third-party service.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The `shopper` workflow accepts a user-supplied endpoint and sends it to a remote API for live probing, but the CLI does not provide a clear disclosure that external systems will receive and interact with that endpoint. This can surprise users, trigger unauthorized testing of third-party services, or leak operational metadata about internal or non-public agent endpoints.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The certification flow transmits agent identifiers and optionally wallet and endpoint data to a remote service without a prominent disclosure at the point of use. This can expose sensitive business metadata or internal service locations to an external party when operators may assume the tool is primarily local due to its framing around scanning and compliance.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The module docstring advertises 'Scan code for 102 security patterns,' but the DANGEROUS_PATTERNS table in code contains only 19 regex checks across five categories. This is a direct contradiction between documentation and implementation, not merely omitted detail.