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## 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
## 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.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def extract_prefix(callsign):
    """Extract DXCC prefix from callsign (simplified)"""
    # Remove /P, /M, /QRP, etc.
    call = callsign.split('/')[0]
    
    # Common special prefixes
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).

Session Persistence

Medium
Category
Rogue Agent
Content
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
```
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.

Session Persistence

Medium
Category
Rogue Agent
Content
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
```
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.

Lp3

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.

Session Persistence

Medium
Category
Rogue Agent
Content
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
```
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.

Context-Inappropriate Capability

Medium
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
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.

Missing User Warnings

Medium
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.

Session Persistence

Medium
Category
Rogue Agent
Content
def load_state() -> Dict:
    """Load previous state."""
    try:
        with open(STATE_FILE) as f:
            return json.load(f)
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.

Missing User Warnings

Low
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.

Missing User Warnings

Low
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.

Intent-Code Divergence

Low
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.

Missing User Warnings

Low
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.

Static analysis

No suspicious patterns detected.