Back to skill

Security audit

RDA MSG Board

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to control an LED message board as advertised, but it handles board passwords in ways users may not expect or understand.

Review before installing. Use this only for boards on a trusted network, do not send confidential messages, avoid default or reused passwords, and treat boards.yaml as a secret file. Prefer HTTPS or an isolated management network where possible, and require explicit user confirmation for ambiguous send requests.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_message.py:67
Finding
Board Credentials and Message Content Transmitted over Plaintext HTTP## Vulnerability Details **File Location**: `scripts/send_message.py`, lines 67-100 **Vulnerability Type**: Plaintext transmission of credentials and sensitive message data **Risk Level**: High ### Vulnerable Code ```python # Handle hostname vs IP (simple check, user must provide scheme or we assume http) if ip.startswith("http"): url = f"{ip}/api" else: url = f"http://{ip}/api" # Build payload payload = {"MSG": args.message} if args.repeat is not None: payload["REP"] = args.repeat if args.buzzer is not None: payload["BUZ"] = args.buzzer if args.delay is not None: payload["DEL"] = args.delay if args.brightness is not None: payload["BRI"] = args.brightness if args.chirp is not None: payload["ALERTCHIRP"] = args.chirp data = json.dumps(payload).encode('utf-8') # Setup request req = urllib.request.Request(url, data=data, method='POST') req.add_header('Content-Type', 'application/json') # Basic Auth auth_str = f"{user}:{password}" b64_auth = base64.b64encode(auth_str.encode('utf-8')).decode('utf-8') req.add_header('Authorization', f"Basic {b64_auth}") ``` ### Technical Analysis The script sends the board username, password, and message through an HTTP request. Base64 encoding is required by HTTP Basic Authentication, but it provides no confidentiality and can be trivially decoded. When the supplied destination does not begin with `http`, the script explicitly constructs a plaintext `http://` URL. It also accepts a caller-controlled URL through `--ip` without restricting the destination to approved board addresses or requiring HTTPS. Consequently, credentials loaded from environment variables or `boards.yaml` can be sent to a caller-selected endpoint. The network transmission itself is necessary for the Skill's declared message-board functionality. The use of plaintext HTTP and unrestricted destinations, however, does not follow least-trust principles and exposes more sensitive information ...[truncated 1234 chars]
Remediation
## Remediation Suggestions - Require HTTPS for authenticated connections and retain standard TLS certificate and hostname validation. - Reject plaintext HTTP unless the user explicitly enables a documented legacy mode after receiving a security warning. - Parse destinations with `urllib.parse.urlsplit` and permit only approved schemes, hosts, and ports. - Maintain an explicit allowlist of configured board addresses rather than accepting arbitrary credential-bearing URLs. - Reject URL user information, unexpected paths, fragments, and malformed hostnames. - Ensure authorization headers are never forwarded across redirects to a different origin; preferably disable redirects for this API request. - Avoid default credentials and require users to configure unique board passwords. - Where the target hardware cannot support TLS, use a trusted local HTTPS gateway, an authenticated VPN, or an isolated management network.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/manage_boards.py:29
Finding
Board Passwords Stored in Plaintext without Explicit Permission Hardening## Vulnerability Details **File Location**: `scripts/manage_boards.py`, lines 29-32 and 64-68 **Vulnerability Type**: Insecure local storage of authentication credentials **Risk Level**: Medium ### Vulnerable Code ```python def save_profiles(profiles): """Save board profiles to boards.yaml.""" config_path = get_config_path() try: import yaml with open(config_path, 'w') as f: yaml.dump({'profiles': profiles}, f, default_flow_style=False) ``` ```python profiles[args.name] = { 'ip': args.ip, 'user': args.user, 'pass': args.password } save_profiles(profiles) ``` ### Technical Analysis Board passwords are serialized directly into `boards.yaml` as plaintext. The script opens the file using the process's normal creation behavior and does not explicitly set restrictive permissions, verify the permissions of an existing file, or protect updates through an atomic secure-write procedure. The resulting access mode depends on the operating-system environment, current umask, and any preexisting file permissions. In a permissive or shared environment, other local users and processes may be able to read the stored credentials. Profile storage is part of the declared functionality, but unrestricted plaintext credential storage is not the minimum secure mechanism necessary to implement profiles. ### Attack Path 1. A user runs `manage_boards.py add` and supplies a board password. 2. The script places that password into the profile dictionary. 3. `save_profiles` writes the dictionary to `boards.yaml` without explicitly enforcing owner-only permissions. 4. In an environment with permissive file permissions, another local user or compromised process reads the file. 5. The attacker extracts the plaintext board username, password, and address. 6. The attacker uses those credentials to authenticate to the board. ### Impact Assessment Exploitation requires l ...[truncated 389 chars]
Remediation
## Remediation Suggestions - Store passwords in an operating-system credential manager or dedicated secret-management service rather than YAML. - If file-based storage must remain supported, create the file with owner-only mode `0600`. - Verify ownership and permissions before reading an existing configuration file; reject files writable or readable by unauthorized users. - Write updates to a securely created temporary file in the same directory, apply restrictive permissions, flush the data, and atomically replace the destination. - Document that `boards.yaml` contains secrets and must not be committed to source control. - Add `boards.yaml` to `.gitignore` and provide only a secret-free sample configuration. - Use unique, least-privileged credentials for each board.

T08 · Insecure Dependencies

Note
Location
SKILL.md:85
Finding
Unpinned PyYAML Installation Produces a Mutable Dependency Supply Chain## Vulnerability Details **File Location**: `SKILL.md`, lines 85-88 **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Low ### Vulnerable Code ```markdown ### Optional (for profile support) - **PyYAML**: `pip install pyyaml` (enables profile-based configuration) ``` ### Technical Analysis The setup instructions install PyYAML without a version constraint, lockfile, package hash, isolated environment, or explicit trusted index. Although PyYAML is a legitimate dependency and the scripts use `yaml.safe_load`, the installation command resolves a mutable future package release. This makes installation non-reproducible and means the code ultimately executed by the Skill may differ from the dependency version that was reviewed. Exploitation depends on compromise of the configured package source, account, artifact, or dependency resolution environment; no evidence of an intentionally malicious dependency was found in the audited project. ### Attack Path 1. A user follows the documented `pip install pyyaml` instruction. 2. Pip resolves the current package version and artifact from its configured package source. 3. If that source, account, artifact, or local package-index configuration has been compromised, an unreviewed package is installed. 4. Malicious dependency code can execute during installation or when imported by the profile-management scripts. 5. The code runs with the privileges of the user operating the Skill. ### Impact Assessment A compromised dependency could access files, environment variables, stored board credentials, and network resources available to the invoking user. It could also alter Skill behavior or execute arbitrary code with that user's privileges. The practical risk is lower because exploitation requires an external supply-chain compromise and no malicious package is present in the reviewed files.
Remediation
## Remediation Suggestions - Pin PyYAML to a reviewed, compatible version. - Record dependencies in a lockfile or requirements file with cryptographic hashes. - Install dependencies in an isolated virtual environment rather than modifying the global Python environment. - Use an explicitly configured trusted package index. - Regularly review and update pinned versions after assessing security advisories. - Document a reproducible installation command, such as `pip install --require-hashes -r requirements.txt`.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill is presented as a simple message-sending utility, but the documented behavior also includes managing profiles, storing credentials, and modifying local configuration state. That mismatch can mislead users and agents into approving a broader capability set than intended, including credential persistence and file writes, which increases the risk of unauthorized configuration changes or secret exposure.

Exfiltration Commands

High
Category
Prompt Injection
Content
sys.exit(1)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Send message to RDA MSG Board")
    parser.add_argument("message", nargs='?', help="Message text to display")
    parser.add_argument("--profile", "-p", help="Board profile name (from boards.yaml)")
    parser.add_argument("--list-profiles", action="store_true", help="List available board profiles")
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill directs use of execution capabilities, environment variables, local configuration files, and network access, but it does not declare any explicit tool scope or permission boundaries. This creates unnecessary ambient authority: an agent may invoke broader tools than users expect, increasing the chance of unintended file modification, credential handling, or outbound network actions.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill does not warn users that messages and credentials may be sent over HTTP to a physical device, which may be unencrypted and observable on the local network. Without a clear warning, users may unknowingly transmit sensitive content or rely on insecure default credentials, leading to interception or unauthorized device access.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger guidance includes a very broad activation phrase such as 'Send [message]', which can cause the skill to activate on generic user requests without clear intent to use this specific device. Overbroad routing can lead to accidental transmission of arbitrary content to a physical board and unexpected network/device actions.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script persists board credentials to a local YAML file, which expands the skill from simple message sending into local secret storage and configuration management. Storing usernames and passwords on disk without access controls, encryption, or clear warning increases the chance of credential disclosure from local compromise, backups, repository inclusion, or accidental sharing.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest frames the skill as a notification/alert sender for a physical LED matrix, but this file implements listing, adding, and removing board profiles instead of message transmission. That administrative behavior is not an obvious implementation detail of 'send scrolling text messages' as currently described.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script writes credential-bearing configuration to disk with no warning and no apparent permission hardening, creating a realistic risk that operators will unknowingly leave reusable device credentials in plaintext. In the context of a skill for controlling physical LED boards over HTTP/JSON, exposed credentials could enable unauthorized message posting or tampering with device settings.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The remove command deletes a named profile from the persisted boards.yaml file immediately when invoked. Although the success message is printed afterward, there is no prior confirmation prompt or warning before this destructive operation occurs.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script defaults to plain HTTP when the target does not already start with 'http', and it sends HTTP Basic Authentication credentials in the Authorization header. That exposes both credentials and message contents to interception or modification by anyone able to observe or tamper with network traffic, especially on shared or untrusted networks.

Static analysis

No suspicious patterns detected.