Back to skill

Security audit

kaspa-wallet

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Kaspa wallet tool, but it can move real funds with weak safeguards and relies on sensitive wallet secrets and an unpinned wallet dependency.

Review before installing or using with real funds. Use testnet or small balances first, avoid putting production mnemonics or private keys in shell environments or agent-visible logs, require a manual out-of-band confirmation for every send, use only trusted wss:// RPC endpoints, and pin/review the kaspa dependency before handling valuable wallets.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T08 · Insecure Dependencies

Error
Location
requirements.txt:1
Finding
Unpinned Security-Critical Wallet Dependency<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1`; dependency installation occurs at `install.py:128-150` **Vulnerability Type**: Uncontrolled third-party dependency installation **Risk Level**: High ### Vulnerable Code `requirements.txt:1`: ```text kaspa ``` `install.py:128-150`: ```python def install_dependencies(venv_python: Path) -> None: """Install dependencies from requirements.txt.""" if not REQ_FILE.exists(): error(f"requirements.txt not found at {REQ_FILE}") error("Create it with: kaspa") raise FileNotFoundError(str(REQ_FILE)) # Set pip cache directory pip_cache = ROOT / ".pip-cache" os.environ["PIP_CACHE_DIR"] = str(pip_cache) log("Installing dependencies...") pip_cmd = [str(venv_python), "-m", "pip", "install", "--upgrade", "pip"] try: run_command(pip_cmd, capture=True) except subprocess.CalledProcessError: log("Warning: Could not upgrade pip, continuing anyway...") pip_install = [str(venv_python), "-m", "pip", "install", "-r", str(REQ_FILE)] try: run_command(pip_install) except subprocess.CalledProcessError as e: ``` ### Technical Analysis The `kaspa` dependency has no exact version constraint or package hash. Every new installation can therefore retrieve whichever release currently satisfies the unrestricted package name. The installer also automatically upgrades `pip` without pinning its version. This dependency is security-critical rather than an isolated utility. The wallet passes mnemonic phrases and private keys into APIs imported from this package and uses it to derive keys, construct transactions, sign payments, and communicate with Kaspa nodes. Consequently, the effective trusted codebase can change after the project itself has been reviewed. No evidence establishes that the current `kaspa` package is malicious. The vulnerability is the absence of controls that prevent a compromised, malicious, or incompatib ...[truncated 1396 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `kaspa` to a specifically reviewed release, for example with an exact `==` constraint. 2. Generate a lock file that includes all transitive dependencies and platform-specific artifacts. 3. Require cryptographic package hashes during installation, such as through pip's `--require-hashes` option. 4. Remove the automatic unrestricted `pip` upgrade or pin and hash the approved pip release. 5. Install only from a documented, trusted package index and disable unintended extra indexes. 6. Verify the package publisher, release provenance, signatures, and source repository before updating. 7. Review dependency updates before changing the lock file, especially code paths that process private keys or sign transactions. 8. Consider isolating signing from third-party networking code so wallet secrets are exposed to the smallest possible trusted component. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/kaswallet.py:198
Finding
Custom RPC Configuration Permits Unencrypted WebSocket Connections<![CDATA[ ## Vulnerability Details **File Location**: `scripts/kaswallet.py:198-205`; plaintext support is documented at `scripts/kaswallet.py:633` **Vulnerability Type**: Insecure transport and insufficient RPC endpoint validation **Risk Level**: Medium ### Vulnerable Code `scripts/kaswallet.py:198-205`: ```python async def rpc_client(): kaspa = load_kaspa() Resolver = getattr(kaspa, "Resolver", None) RpcClient = getattr(kaspa, "RpcClient", None) if not Resolver or not RpcClient: raise RuntimeError("kaspa SDK missing Resolver/RpcClient") if RPC_URL: client = RpcClient(url=RPC_URL, network_id=NETWORK) ``` The command help at `scripts/kaswallet.py:633` explicitly permits plaintext WebSocket endpoints: ```text KASPA_RPC_URL Optional direct wRPC url (ws:// or wss://) ``` ### Technical Analysis `KASPA_RPC_URL` is passed directly to the SDK without validating its scheme, hostname, or transport security. The documented acceptance of `ws://` allows RPC messages to travel without TLS encryption or server authentication. The RPC server supplies balances, fee estimates, network status, and UTXO data used by wallet operations. A plaintext connection allows an on-path attacker to observe requests and responses, modify RPC data, impersonate the configured node, censor transaction submissions, or return misleading wallet state. Transaction signing occurs locally, and the reviewed project does not explicitly transmit the mnemonic or private key to the RPC endpoint. Therefore, this issue does not by itself demonstrate direct key disclosure. Transaction outputs are also created locally from command-line arguments, limiting the endpoint's ability to redirect funds through this code alone. The primary risks are RPC privacy loss, integrity violations, misleading results, denial of service, and interference with transaction submission. ### Attack Path 1. A user or automated agent configures `KASPA_RPC_URL` with a `ws://` endpo ...[truncated 1355 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept only `wss://` custom RPC URLs by default. 2. Reject `ws://` and all unrelated URL schemes before constructing the RPC client. 3. If plaintext RPC is required for local development, place it behind an explicit opt-in flag and restrict it to loopback addresses. 4. Ensure TLS certificate and hostname verification cannot be disabled silently. 5. Validate that the connected RPC server reports the expected Kaspa network before performing wallet operations. 6. Treat all RPC responses as untrusted and validate UTXO structure, network identifiers, values, fees, and transaction summaries before signing. 7. Present the final transaction destination, amount, change address, and total fee from the actual unsigned transaction immediately before signing. 8. Document the trust implications of custom RPC endpoints and recommend user-operated or otherwise trusted TLS-protected nodes. ]]>
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill description omits outbound network/RPC behavior and extra capabilities such as fee estimation and network inspection. In a cryptocurrency context, undeclared network interactions matter because they can leak wallet-related metadata and change trust assumptions about external endpoints.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill description omits outbound network/RPC behavior and extra capabilities such as fee estimation and network inspection. In a cryptocurrency context, undeclared network interactions matter because they can leak wallet-related metadata and change trust assumptions about external endpoints.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The README instructs users to place highly sensitive wallet secrets in environment variables but does not warn that environment variables can be exposed through shell history, process inspection, logs, CI systems, crash reports, or inherited subprocess environments. In the context of a cryptocurrency wallet, exposure of a private key or mnemonic can immediately lead to total theft of funds.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README documents a live cryptocurrency transfer command without warning that blockchain transactions are typically irreversible and that mistakes in recipient address or amount can permanently destroy funds. In an agent-facing wallet skill, this is more dangerous because users or automation may execute examples directly without appreciating the financial finality.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README advertises seed phrase generation without warning that the mnemonic is the root credential for the wallet and that losing it can permanently prevent wallet recovery, while disclosure allows theft. In wallet software, omission of backup and confidentiality guidance materially increases the chance of irreversible asset loss.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill documents shell execution, environment-variable secrets, package installation, and network/RPC access, but does not declare any explicit tool scope or permissions. In an agent setting this can cause unsafe execution assumptions, allowing a wallet-related skill to invoke local shell and access sensitive environment data without clear review boundaries.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
**Troubleshooting install:**
- If pip fails: `pip install kaspa` manually, or try `KASPA_PYTHON=python3.12 python3 install.py`
- If venv missing: `sudo apt install python3-venv` (Ubuntu/Debian)
- To reinstall: `rm -rf .venv && python3 install.py`

## Environment Variables
Confidence
72% confidence
Finding
The troubleshooting guidance recommends using sudo to install system packages. While common in documentation, encouraging privileged execution in a skill context is risky because it normalizes elevation and can lead users or agents to perform setup actions with unnecessary root privileges, amplifying the effect of mistakes or compromised dependencies.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation instructs users to place a private key or mnemonic directly into shell environment variables without a prominent warning about process-environment exposure. Environment variables can leak through shell history, process inspection, crash reports, CI logs, or inherited subprocesses, making compromise of wallet funds possible.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The send-payment workflow lacks an explicit warning that blockchain transfers are real, value-bearing, and typically irreversible. In a wallet skill, this omission materially increases the chance of operator error, misdirected funds, or accidental live transactions by users or agents treating the command as low-risk.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
error(f"Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ required, found {sys.version_info.major}.{sys.version_info.minor}")
        error("Please install a newer Python version:")
        error("  macOS:   brew install python@3.12")
        error("  Ubuntu:  sudo apt install python3.12")
        error("  Windows: Download from python.org")
        return False
    return True
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
error(f"Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ required, found {sys.version_info.major}.{sys.version_info.minor}")
        error("Please install a newer Python version:")
        error("  macOS:   brew install python@3.12")
        error("  Ubuntu:  sudo apt install python3.12")
        error("  Windows: Download from python.org")
        return False
    return True
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_command(cmd: list[str], capture: bool = False) -> subprocess.CompletedProcess:
    """Run a command with proper error handling."""
    try:
        return subprocess.run(
            cmd,
            cwd=str(ROOT),
            capture_output=capture,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The send command signs and broadcasts cryptocurrency transactions immediately based on command-line arguments, with no interactive confirmation, recipient verification prompt, dry-run default, or explicit safety warning. In an agent skill context, this is more dangerous because an LLM or automation layer could trigger irreversible fund transfers from ambiguous, injected, or misunderstood instructions.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The mnemonic generation command prints the newly generated wallet seed phrase directly to stdout without warning, masking, secure storage guidance, or output-channel restrictions. In an agent environment, stdout may be logged, persisted, or exposed to other tools, which can result in complete wallet compromise if the seed is captured.

Unpinned Dependencies

Low
Category
Supply Chain
Content
kaspa
Confidence
95% confidence
Finding
The dependency is unpinned, so installs may resolve to different versions over time, including versions with breaking changes or newly introduced malicious or vulnerable code. In a cryptocurrency wallet skill, this is more dangerous than usual because dependency compromise could affect key handling, address generation, transaction construction, or fund transfers.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
return obj[name]
        else:
            if hasattr(obj, name):
                return getattr(obj, name)
    return default
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
# First, try to get string from Address objects using to_string() method
    if not isinstance(value, str) and value is not None:
        for name in ("to_string", "toString", "as_string", "asString"):
            fn = getattr(value, name, None)
            if fn:
                try:
                    s = fn()
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
s = sanitize_address_input(value)

    for name in ("try_from", "tryFrom", "from_string", "fromString", "parse"):
        fn = getattr(Address, name, None)
        if fn:
            return fn(s)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def address_to_string(kaspa: Any, addr_obj: Any) -> str:
    for name in ("to_string", "toString", "as_string", "asString"):
        fn = getattr(addr_obj, name, None)
        if fn:
            try:
                return sanitize_address_input(fn())
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def call_any(obj: Any, names: list[str], *args: Any) -> Any:
        for name in names:
            fn = getattr(obj, name, None)
            if fn:
                return fn(*args)
        raise AttributeError(f"Missing method(s): {', '.join(names)}")
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.