Back to skill

Security audit

Amateur Radio DX Monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do ham-radio DX monitoring, but it belongs in Review because it stores state in an unsafe shared temp path and encourages recurring automation.

Review before installing. Run only as a non-root user, avoid enabling cron until the state file is moved to a private per-user directory, and be aware that your callsign may be sent to DX cluster servers while local config may store location details.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
dx-monitor.py:203
Finding

Predictable Temporary State File Allows Symlink-Based File Overwrite

Content
View full analysis

Vulnerability Details

File Location: dx-monitor.py:20 and dx-monitor.py:203-214
Vulnerability Type: Unsafe temporary-file handling and symbolic-link following
Risk Level: Medium

python
STATE_FILE = "/tmp/dx-monitor-state.json"
python
def load_state() -> Dict:
    """Load previous state."""
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
    except:
        return {'last_spots': [], 'last_check': 0}


def save_state(state: Dict):
    """Save state."""
    with open(STATE_FILE, 'w') as f:
        json.dump(state, f, indent=2)

Technical Analysis

The monitor stores state at the fixed, globally predictable path /tmp/dx-monitor-state.json. The file is opened using ordinary open() operations without:

  • Verifying that the path is not a symbolic link.
  • Verifying ownership or file type.
  • Creating an owner-only state directory.
  • Setting explicit restrictive permissions.
  • Using exclusive or atomic file creation.
  • Writing to a protected temporary file and atomically replacing the destination.

On systems where applicable symbolic-link protections are absent, disabled, or bypassable, another local user can create the expected path as a symbolic link before the monitor creates it. When save_state() opens the path using write mode, Python follows the link and truncates the linked destination.

The broad exception handler in load_state() also conceals ownership, format, and access errors, making manipulated state appear equivalent to missing state and reducing visibility into an attack.

Attack Path

  1. The attacker confirms that the victim runs dx-monitor.py watch --new-only, potentially through the documented recurring cron configuration.
  2. Before the victim creates the state file, the attacker creates /tmp/dx-monitor-state.json as a symbolic link to a chosen file.
  3. The chosen target must be writable by the account tha ...[truncated 1642 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace the shared /tmp path with an account-specific state directory, preferably:
    • $XDG_STATE_HOME/ham-radio-dx/state.json, or
    • ~/.local/state/ham-radio-dx/state.json.
  2. Create the parent directory with permissions 0700.
  3. Create state files with permissions 0600.
  4. Reject symbolic links and non-regular files using os.lstat() and, where supported, os.open() with O_NOFOLLOW.
  5. Write to a temporary file in the same protected directory, flush and synchronize it, and then use os.replace() for atomic publication.
  6. Verify that any existing state file is owned by the current effective user.
  7. Catch specific exceptions and report unsafe ownership, file-type, and permission conditions rather than silently treating all errors as missing state.
  8. Correct the documentation inconsistency: SKILL.md states both ~/dx-monitor-state.json and /tmp/dx-monitor-state.json. Document only the hardened account-specific location.

A secure implementation should follow this pattern:

python
import json
import os
import tempfile
from pathlib import Path

state_root = Path(
    os.environ.get(
        "XDG_STATE_HOME",
        Path.home() / ".local" / "state"
    )
) / "ham-radio-dx"

state_root.mkdir(mode=0o700, parents=True, exist_ok=True)
os.chmod(state_root, 0o700)
state_file = state_root / "state.json"

def save_state(state):
    fd, temporary_path = tempfile.mkstemp(
        prefix=".state-",
        dir=state_root
    )
    try:
        os.fchmod(fd, 0o600)
        with os.fdopen(fd, "w") as output:
            json.dump(state, output, indent=2)
            output.flush()
            os.fsync(output.fileno())
        os.replace(temporary_path, state_file)
    except Exception:
        try:
            os.unlink(temporary_path)
        except FileNotFoundError:
            pass
        raise
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding

The declared purpose emphasizes DX monitoring and digests, but the documentation also introduces interactive local configuration, persistence, and invocation of another script in AI mode without clearly declaring those operational behaviors. Description/behavior mismatches are dangerous because users and reviewers may approve a skill for one purpose while it performs additional local actions such as storing user data or spawning subprocesses. Here the extra behavior is not obviously hostile, but it is insufficiently disclosed.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · dx-ai-enhanced.py (reported line 254)May include surrounding context.

python
def extract_prefix(callsign):
    """Extract DXCC prefix from callsign (simplified)"""
    # Remove /P, /M, /QRP, etc.
    call = callsign.split('/')[0]
    
    # Common special prefixes

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% 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.

Content

Scanner excerpt · README.md (reported line 34)May include surrounding context.

Or add to your crontab (as non-root user):

bash
crontab -e
# Add: */5 * * * * /path/to/dx-monitor.py watch --new-only --callsign YOUR_CALL >> ~/dx.log 2>&1

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% 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.

Content

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

Or add to your crontab (as non-root user):

bash
crontab -e
# Add: */5 * * * * /path/to/dx-monitor.py watch --new-only --callsign YOUR_CALL >> ~/dx.log 2>&1

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill documentation directs users to run local Python scripts, write state/log files, and configure cron jobs, but it does not declare any tool scope such as file or shell access. Missing scope declarations reduce transparency and can cause the skill to be executed with broader capabilities than users expect. In this context the behavior appears operationally relevant rather than overtly malicious, but it still creates a trust and review gap.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

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

Use the OpenClaw cron tool to set up monitoring:

bash
# Create a cron job for DX alerts (every 5 minutes)
cron add --name "DX Monitor" --schedule "*/5 * * * *" --payload 'systemEvent:Check DX cluster for rare spots' --sessionTarget main

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Launching a separate script with subprocess is a powerful execution capability distinct from simply monitoring DX clusters and producing digests. The manifest does not indicate any need for process spawning, and this implementation choice materially expands what the skill can do compared with its stated purpose.

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 · dx-ai-enhanced.py (reported line 300)May include surrounding context.

python
dx_monitor = os.path.join(script_dir, 'dx-monitor.py')
    
    try:
        result = subprocess.run(
            ['python3', dx_monitor, 'watch', '--new-only'],
            capture_output=True,
            text=True,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The setup flow collects and persists a detailed operator profile including callsign, grid square, derived latitude/longitude, power, and DX preferences to a local JSON file. For a monitoring skill, this is more data than minimally necessary and creates a privacy exposure if the host is shared, backed up, or otherwise accessed by other local users or software.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The code writes a configuration file containing sensitive location information, including grid square and derived latitude/longitude, without warning the user about persistence or protecting the file. This can leak a radio operator's approximate or precise location to other local users, backup systems, or malware on the host.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
77% confidence
Finding

The skill persists session-like history in a predictable world-writable temporary path (/tmp/dx-monitor-state.json) and reloads it on future runs without integrity or ownership checks. In a multi-user system, another local user could pre-create, modify, or replace this file via symlink/hardlink tricks to influence output, overwrite other files accessible to the process, or cause misleading persistence behavior.

Content

Scanner excerpt · dx-monitor.py (reported line 203)May include surrounding context.

python
def load_state() -> Dict:
    """Load previous state."""
    try:
        with open(STATE_FILE) as f:
            return json.load(f)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The README instructs the user to create a local configuration file containing callsign, grid square, precise latitude/longitude, and station details, but provides no warning about privacy, file permissions, or avoiding accidental publication. While this is common for ham-radio tooling, the data could expose a user's home location and operating profile if the config is committed to version control, shared, or read by other local users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill connects to external DX cluster nodes over the network and may transmit the user's callsign, but this privacy-relevant behavior is not clearly warned about in the main description. Even if expected for ham radio tooling, undisclosed outbound communication can expose identifying information and operating habits to third parties. The skill context makes this somewhat less severe because network use is core to the feature, but disclosure is still required.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The documentation states at L105 that state is saved to ~/dx-monitor-state.json, but the technical details at L164 say state tracking uses /tmp/dx-monitor-state.json. These two statements cannot both be true and create an intent/documentation divergence about where persistent monitoring state is actually stored.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The code persists monitoring state to /tmp/dx-monitor-state.json, which is a file write affecting local system state. Although the function has an internal docstring, there is no user-facing warning in the CLI output or argument help that running with stateful behavior will create or modify this file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.