Back to skill

Security audit

Runstr analytics

Security checks for vulnerabilities and agentic risk

Overview

This RUNSTR analytics skill appears purpose-aligned, but it handles a full Nostr private key in unsafe and partly misdescribed ways, so users should review it carefully before installing.

Install only if you are comfortable giving this skill and the local nak binary access to your full Nostr private key and decrypted RUNSTR health/journal data. Avoid pasting the key into chat or using --nsec on shared or monitored systems, review the cron job before enabling it, and consider removing cached data under ~/.cache/runstr-analytics when no longer needed.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (3)

T06 · System Persistence

Warning
Location
setup_cron.sh:20
Finding
User-Level Persistence Through a Recurring Cron Job<![CDATA[ ## Vulnerability Details **File Location**: `setup_cron.sh:20-49` **Vulnerability Type**: Scheduled-task persistence **Risk Level**: Medium ### Vulnerable Code ```bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CRON_JOB="0 7 * * * $SCRIPT_DIR/daily_update.sh >> $HOME/.cache/runstr-analytics/cron.log 2>&1" # Check if cron job already exists if crontab -l 2>/dev/null | grep -q "runstr-analytics/daily_update.sh"; then echo "⚠️ Cron job already exists." echo "" echo "Current crontab entry:" crontab -l | grep "runstr-analytics" echo "" read -p "Replace existing job? (y/N): " REPLACE if [[ $REPLACE =~ ^[Yy]$ ]]; then # Remove old entry crontab -l 2>/dev/null | grep -v "runstr-analytics/daily_update.sh" | crontab - echo "Old job removed." else echo "Setup cancelled." exit 0 fi fi # Add new cron job (crontab -l 2>/dev/null; echo "$CRON_JOB") | crontab - ``` ### Technical Analysis The setup script modifies the current user's crontab and registers `daily_update.sh` to execute every day at 07:00. This creates execution that survives the current Skill invocation and user session, meeting the definition of system persistence. The behavior is disclosed, supports the declared optional automated-update feature, and requires interactive confirmation before installation. It is therefore not covert. Nevertheless, scheduled execution exceeds the privileges needed for one-shot fitness analysis and expands the security boundary from an explicitly invoked program to persistent background execution. The cron entry references the project script by path rather than an immutable, integrity-checked installed artifact. Any future modification or replacement of `daily_update.sh` at that path will affect what cron executes. The command also does not quote the script and log paths for cron, which may cause incorrect interpretation when installation paths contain spaces or shell meta ...[truncated 1102 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Keep scheduling strictly optional and separate it from the core analytics installation. 2. Display the exact cron expression and command before requesting consent. 3. Install the executable into a user-owned directory with restrictive permissions rather than referencing a mutable project checkout. 4. Quote or safely escape all paths used in the cron command. 5. Verify ownership and permissions of the target script before registration and before each execution. 6. Provide a dedicated uninstall command that removes only the exact entry created by the Skill. 7. Prefer a user-level service manager with explicit enable, disable, status, and logging controls where supported. 8. Consider generating reports only on explicit invocation, which is the minimum-privilege design for the core analytics functionality. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
daily_update.sh:62
Finding
Nostr Private Keys Are Exposed Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Locations**: - `daily_update.sh:62-67` - `scripts/analyze.py:46-55` - `scripts/analyze.py:111` - `scripts/analyze_light.py:35-44` - `scripts/analyze_light.py:102` - `scripts/analyze_extended.py:107-108` - `scripts/analyze_extended.py:162` - `SKILL.md:41-58` **Vulnerability Type**: Sensitive credential exposure through process arguments **Risk Level**: High ### Vulnerable Code The scheduled update passes the original Nostr private key as a Python command-line argument: ```bash python3 scripts/analyze_extended.py \ --nsec "$NSEC" \ --days 60 \ --insights \ --force-refresh > "$REPORT_FILE" 2>&1 ``` The full analyzer passes the original `nsec` and its decoded secret key to `nak` through command-line arguments: ```python def decode_nsec(self) -> bool: """Decode nsec to hex keys.""" try: result = subprocess.run( ["nak", "decode", self.nsec], capture_output=True, text=True, check=True ) self.hex_sk = result.stdout.strip() result = subprocess.run( ["nak", "key", "public", self.hex_sk], capture_output=True, text=True, check=True ) ``` ```python cmd = ["nak", "encrypt", "--sec", self.hex_sk, self.hex_pk, "--decrypt"] result = subprocess.run( cmd, input=content, capture_output=True, text=True, check=True ) ``` The lightweight analyzer has the same issue: ```python result = subprocess.run( ["nak", "decode", self.nsec], capture_output=True, text=True, check=True ) self.hex_sk = result.stdout.strip() result = subprocess.run( ["nak", "key", "public", self.hex_sk], capture_output=True, text=True, check=True ) ``` ```python cmd = ["nak", "decrypt", "--sec", self.hex_sk, "--sender-pubkey", self.hex_pk, content] result = subprocess.run(cmd, capture_output=True, text=True, check=True) ``` The extended analyzer protects only the initial decode operation. It subsequently exposes the decoded ...[truncated 3201 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--nsec` command-line option from all supported and documented execution paths. 2. Read the private key from standard input, a protected file descriptor, or a narrowly scoped operating-system credential store. 3. Ensure every `nak` operation receives secret material through a non-`argv` mechanism supported by the installed `nak` version. 4. Do not pass the decoded hexadecimal secret key to `nak key public` or decryption commands as a positional argument or `--sec` value if that places it in `argv`. 5. Keep the secret only in memory for the shortest practical duration and clear references after use where feasible. 6. Update `daily_update.sh` to provide the secret through the selected protected channel rather than `--nsec`. 7. Remove all documentation examples that place an actual private key on the command line. 8. Avoid instructing users to send private keys through chat. Prefer local interactive credential entry or an operating-system secret store. 9. Correct the security documentation so it accurately describes every process boundary through which the key passes. 10. Add automated tests that inspect child-process arguments and fail if an `nsec` or decoded private key appears. 11. Advise users who have executed the affected commands on monitored or multi-user systems to assess possible exposure and rotate to a new Nostr identity where appropriate. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:4
Finding
Mutable and Unpinned Third-Party Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:4` **Vulnerability Type**: Unpinned executable and Python dependencies **Risk Level**: Medium ### Vulnerable Code ```yaml metadata: {"openclaw":{"emoji":"📊","requires":{"bins":["nak","python3"],"python_packages":["pandas","numpy","scipy","requests"]},"install":[{"id":"go","kind":"go","package":"github.com/fiatjaf/nak@latest","bins":["nak"],"label":"Install nak via Go"},{"id":"python_deps","kind":"pip","packages":["pandas","numpy","scipy","requests"],"label":"Install Python analytics dependencies"}]}} ``` ### Technical Analysis The Skill requests installation of `github.com/fiatjaf/nak@latest`, which is a mutable dependency reference. The Python packages are also specified without exact versions or integrity hashes. Consequently, the code installed in the future can differ from the components present when the Skill was reviewed. This does not establish that any named dependency is currently malicious. The confirmed weakness is that installation is not reproducible and does not constrain dependency resolution to reviewed artifacts. Because `nak` handles the user's private key and decrypted backup operations, compromise or unexpected behavior in that dependency would be especially sensitive. The declared `requests` dependency does not appear to be imported by the three reviewed Python scripts, unnecessarily expanding the dependency surface. ### Attack Path 1. The user installs the Skill's declared dependencies. 2. The installer resolves `nak@latest` and unconstrained Python package versions at installation time. 3. An upstream account, release, distribution channel, or transitive dependency is compromised, or a future release introduces unsafe behavior. 4. The package manager downloads and installs code that was not part of the audited artifact. 5. The dependency executes with the installing user's privileges or later handles the user's Nostr private key and sensitive fitness data. ### Impa ...[truncated 626 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `github.com/fiatjaf/nak@latest` with a reviewed immutable release version or commit. 2. Verify downloaded executable artifacts using published checksums or signatures. 3. Pin exact Python dependency versions in a lockfile. 4. Require package hashes, such as through pip's hash-checking mode, for reproducible installation. 5. Review and pin transitive dependencies where the deployment mechanism supports it. 6. Remove `requests` from the declared dependencies unless functionality requiring it is added. 7. Document the reviewed dependency versions and establish a controlled process for updates. 8. Re-audit dependency changes before advancing pinned versions, especially because `nak` processes secret key material. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (42)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill presents itself as an analytics tool but also includes system-level automation via cron installation and execution of local update scripts. System modification and scheduled execution materially expand the attack surface because they create persistence and recurring execution, which is more dangerous than one-off local analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The skill presents itself as an analytics tool but also includes system-level automation via cron installation and execution of local update scripts. System modification and scheduled execution materially expand the attack surface because they create persistence and recurring execution, which is more dangerous than one-off local analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill presents itself as an analytics tool but also includes system-level automation via cron installation and execution of local update scripts. System modification and scheduled execution materially expand the attack surface because they create persistence and recurring execution, which is more dangerous than one-off local analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill presents itself as an analytics tool but also includes system-level automation via cron installation and execution of local update scripts. System modification and scheduled execution materially expand the attack surface because they create persistence and recurring execution, which is more dangerous than one-off local analysis.

Context-Inappropriate Capability

High
Confidence
94% confidence
Finding
The manifest emphasizes analytics, insights, and coaching, but this code connects to multiple external WebSocket relays to request backup events. Network backup retrieval is not an obvious requirement of the described analytical purpose unless explicitly declared as part of the skill's scope.

Context-Inappropriate Capability

High
Confidence
93% confidence
Finding
A fitness analytics skill invoking external binaries for key operations increases trust and supply-chain risk because the security of secret handling depends on an external tool outside the script's control. If the nak binary is malicious, replaced in PATH, or behaves unexpectedly, it can exfiltrate private keys or decrypted data.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill declares no explicit tool scope even though the documentation clearly implies shell execution, local file access, network access to Nostr relays, and cron setup. Missing scope/permission declarations increases the chance that an agent or user will invoke broader capabilities than expected, reducing transparency and making misuse harder to review.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The setup instructions explicitly tell users to provide their Nostr private key directly to the bot. Encouraging users to paste private keys into conversational interfaces is dangerous because bot logs, transcripts, telemetry, prompt history, or downstream tools may capture the secret, enabling account compromise and decryption of protected data.

Missing User Warnings

Medium
Confidence
99% confidence
Finding
The quick-start examples pass the private key as a command-line argument, which commonly exposes secrets through shell history, process listings, audit logs, and crash reports. This directly contradicts the later security guidance about stdin and materially increases the risk of credential disclosure during normal use.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The manifest requests a Nostr private key (nsec), which is an extremely sensitive credential because possession of it typically grants full control over the user's identity and signing capabilities. Although the stated purpose is decrypting RUNSTR backup data, the manifest provides no user-facing warning, scope limitation, or safer alternative, increasing the risk that users disclose a credential whose compromise could enable impersonation, unauthorized signing, and account takeover across Nostr-related use.

Intent-Code Divergence

Medium
Confidence
99% confidence
Finding
The script’s security comment says the secret is passed via stdin, but the implementation actually supplies RUNSTR_NSEC as a command-line argument to python3. Command-line arguments are commonly exposed through process listings, monitoring tools, shell history wrappers, crash reports, and some logging setups, so the NSEC may be disclosed to other local users or collected by system telemetry.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def decode_nsec(self) -> bool:
        """Decode nsec to hex keys."""
        try:
            result = subprocess.run(
                ["nak", "decode", self.nsec],
                capture_output=True, text=True, check=True
            )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The manifest describes a fitness analytics skill that analyzes workout history and related wellness data, but the implementation depends on spawning the external `nak` binary for key handling, network retrieval, and decryption. Executing subprocesses is a broader capability than is justified by the stated analytics purpose, especially because it introduces command execution dependency outside the skill's core analysis scope.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
            self.hex_sk = result.stdout.strip()
            
            result = subprocess.run(
                ["nak", "key", "public", self.hex_sk],
                capture_output=True, text=True, check=True
            )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
            # Try nak decrypt first
            cmd = ["nak", "encrypt", "--sec", self.hex_sk, self.hex_pk, "--decrypt"]
            result = subprocess.run(
                cmd, input=content, capture_output=True, text=True, check=True
            )
            decrypted = result.stdout.strip()
Confidence
90% confidence
Finding
This call passes the decrypted-account secret key to an external CLI via command-line arguments (`--sec`, self.hex_sk). On many systems, process arguments are observable by other local processes through tools like `ps` or `/proc`, so a local attacker could recover the secret and fully compromise the user's Nostr identity and encrypted backup access.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
Multiple coaching tips and summary headings are hard-coded in Norwegian, making the skill's natural-language output locale-specific by default. The file does not offer a language choice or explain that the skill is intentionally limited to Norwegian users, which violates the language/locale policy for all file types.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The `--training-plan` option allows reading any local file path and processes its contents, even though the feature is only loosely related to workout analytics. In an agent setting, this broadens the skill from backup analysis into arbitrary local file access, creating a path for unintended data exposure if a prompt or wrapper causes the agent to read sensitive files and then summarize or emit their contents.

Missing User Warnings

Medium
Confidence
99% confidence
Finding
Accepting the Nostr private key through `--nsec` exposes it in shell history, process listings, audit logs, and agent telemetry. Because this key is the root credential for identity and backup decryption, disclosure can let an attacker impersonate the user, decrypt private content, and potentially publish or manipulate data as that user.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script persistently stores decrypted workout history locally, which expands the data exposure surface beyond transient analytics. Even with restrictive file permissions, local compromise, backups, sync tools, or multi-user misconfiguration can expose sensitive health and behavior data.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
Fetching and decrypting a remote backup using the user's private key is a materially more sensitive capability than local analytics alone. In this skill context, that means the tool handles identity secrets and full historical personal data, increasing the risk if the script is modified, misused, or run in an untrusted environment.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Decode nsec to hex keys using stdin to avoid exposing secret in CLI."""
        try:
            # Use stdin instead of CLI args to prevent nsec exposure in ps/process list
            result = subprocess.run(
                ["nak", "decode"],
                input=self.nsec,
                capture_output=True, text=True, check=True
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
            self.hex_sk = result.stdout.strip()
            
            result = subprocess.run(
                ["nak", "key", "public", self.hex_sk],
                capture_output=True, text=True, check=True
            )
Confidence
84% confidence
Finding
The code passes the derived secret key as a command-line argument to an external process. On many systems, process arguments can be exposed to other local users, monitoring tools, logs, or crash reports, which can leak the user's private key material.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Decrypt NIP-44 encrypted content."""
        try:
            cmd = ["nak", "decrypt", "--sec", self.hex_sk, "--sender-pubkey", self.hex_pk, content]
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            decrypted = result.stdout.strip()
            
            import base64
Confidence
95% confidence
Finding
The decryption command includes the user's secret key in the argv list via --sec, which can expose the private key to local process inspection, telemetry, shell history wrappers, or diagnostic logs. Because this key protects the user's Nostr identity and encrypted backup, compromise of it has severe downstream impact.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Accepting a private key through a CLI argument is dangerous because command-line arguments are commonly visible in process listings, job schedulers, shell history wrappers, and logs. In this script, that risk is amplified because the same secret is then used to fetch and decrypt sensitive user backup data.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def decode_nsec(self) -> bool:
        """Decode nsec to hex keys."""
        try:
            result = subprocess.run(
                ["nak", "decode", self.nsec],
                capture_output=True, text=True, check=True
            )
Confidence
93% confidence
Finding
The script passes a user-supplied Nostr private key to an external executable via subprocess, expanding the trust boundary to the local nak binary and the host environment. Even though shell injection is avoided by using an argument list, private key material can still be exposed to process listings, crash logs, telemetry, or a malicious/replaced nak binary.

Static analysis

No suspicious patterns detected.