Back to skill

Security audit

MessageGuard

Security checks for vulnerabilities and agentic risk

Overview

MessageGuard is a coherent local message-filtering skill, but it can expose blocked secrets in its own JSON output and detection logs, so users should review it carefully before relying on it.

Install only if you are comfortable reviewing or patching the script first. Do not rely on it as-is for high-sensitivity messages unless blocked outputs are redacted or omitted, config failures fail closed, and detection logs are minimized, permission-restricted, and retained only where appropriate.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/filter_message.py:201
Finding
YAML Configuration Failure Silently Falls Back to Weaker Defaults<![CDATA[ ## Vulnerability Details **File Location**: `scripts/filter_message.py:201-217`; related dependency claim at `SKILL.md:19` **Vulnerability Type**: Fail-open security configuration caused by an undeclared dependency **Risk Level**: Medium ### Vulnerable Code ```python raw = p.read_text() ext = p.suffix.lower() try: if ext in (".yaml", ".yml"): try: import yaml loaded = yaml.safe_load(raw) except ImportError: # Fallback: minimal YAML parser not available; try json print("[filter_message] WARNING: pyyaml not installed, falling back to json parser", file=sys.stderr) loaded = json.loads(raw) else: loaded = json.loads(raw) cfg.update(loaded) except Exception as e: print(f"[filter_message] WARNING: failed to parse config ({e}), using defaults", file=sys.stderr) return cfg ``` The installation documentation makes the following conflicting claim: ```markdown 2. Navigate to the directory. The skill is dependency-free, relying only on the Python standard library. ``` ### Technical Analysis The documented YAML configuration format requires the third-party PyYAML module, despite the Skill claiming to use only the Python standard library. If PyYAML is unavailable, the implementation attempts to parse the YAML document as JSON. Ordinary YAML is generally not valid JSON, so this operation raises an exception. The outer exception handler only prints a warning and continues with `DEFAULT_CONFIG`. This is a fail-open design: an explicitly selected security policy can be discarded while message processing and transmission continue. Discarded settings can include: - Custom patterns for organization-specific credentials. - Overrides that change sensitive patterns from `mask` or `warn` to `block`. - Required detection logging. - Reduced prefix or suffix disclosure settings. - Other filtering controls expected by the operator. The warning is written to stderr and d ...[truncated 1393 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make configuration failures fail closed: - If an explicitly selected configuration cannot be read or parsed, terminate with exit code 2. - Do not process or emit the outgoing message under fallback defaults. 2. Resolve the dependency contradiction: - Declare and pin PyYAML as a required dependency, or - Remove YAML support and document JSON as the only supported format, or - Implement a safe parser that actually uses only declared dependencies. 3. Validate the parsed root value before calling `cfg.update`: - Require a mapping/object. - Reject null, arrays, strings, and other invalid root types. 4. Validate all security-relevant fields against a strict schema, including actions, pattern definitions, capture groups, and numeric masking settings. 5. Clearly distinguish optional default-file behavior from explicit configuration: - An absent default file may reasonably use defaults. - A supplied but invalid `--config` file must be treated as a fatal policy error. 6. Add automated tests covering missing PyYAML, malformed YAML, malformed JSON, invalid root types, and custom rules that must not be silently discarded. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/filter_message.py:352
Finding
Blocked Messages Are Returned Unredacted in JSON Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/filter_message.py:352-359` and `scripts/filter_message.py:390-391` **Vulnerability Type**: Plaintext sensitive-data exposure through process output **Risk Level**: Medium ### Vulnerable Code ```python result = { "blocked": blocked, "message": message if blocked else filtered, # blocked = send nothing "detections": all_detections, "warnings": warnings } ``` The result, including the original blocked message, is then printed: ```python print(json.dumps(result, indent=2)) sys.exit(1 if result["blocked"] else 0) ``` ### Technical Analysis When any detection has the `block` action, `blocked` becomes true. Instead of returning an empty or sanitized message, the result assigns the complete original input to the `message` field. The command-line entry point serializes this result to stdout before returning exit code 1. Consequently, content identified as sufficiently sensitive to block—such as JWTs, private-key material, AWS keys, SSNs, or other credentials—is duplicated into process output without redaction. The nonzero exit status does not make this behavior safe. Standard integration environments frequently capture stdout in: - CI/CD job logs. - Agent tool transcripts. - Shell variables. - Subprocess telemetry. - Debugging or observability platforms. - Wrapper programs that parse JSON but fail to enforce the exit code. It also makes the API unsafe when a caller trusts the `message` field without first checking `blocked`. ### Attack Path 1. Sensitive content is supplied to the filter. 2. A pattern with the `block` action matches the content. 3. The script sets `blocked` to true. 4. The original, unmodified content is assigned to `result["message"]`. 5. The complete result is printed to stdout. 6. A shell, CI runner, agent framework, or logging system captures stdout. 7. The blocked secret remains exposed in logs or telemetry; alternatively, an incorrectly implemented caller f ...[truncated 581 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never include the original message in a blocked result. For example: ```python result = { "blocked": blocked, "message": "" if blocked else filtered, "detections": all_detections, "warnings": warnings, } ``` 2. Prefer returning a sanitized draft if useful, but ensure every blocking match is fully removed or masked. 3. Design the API to remain safe even when a caller mishandles the exit code: - A blocked response must contain no sendable plaintext secret. - The `message` field should be empty or explicitly omitted for blocked results. 4. Ensure error messages and exception handlers never interpolate the original message. 5. Add regression tests asserting that known blocked values do not occur anywhere in stdout, stderr, returned JSON, or logs. 6. Update integration documentation to require checking both the structured `blocked` value and the process exit code, while not relying on caller correctness as the primary confidentiality control. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/filter_message.py:254
Finding
Detection Metadata and Log Files Disclose Secret Prefixes<![CDATA[ ## Vulnerability Details **File Location**: `scripts/filter_message.py:254-261`, `scripts/filter_message.py:269-276`, and `scripts/filter_message.py:285-297` **Vulnerability Type**: Sensitive metadata disclosure and insufficiently protected local logging **Risk Level**: Low ### Vulnerable Code For capture-group detections: ```python detections.append({ "name": name, "action": action, "snippet": sensitive[:6] + "…" if len(sensitive) > 6 else sensitive, "description": pattern.get("description", "") }) ``` For full-match detections: ```python detections.append({ "name": name, "action": action, "snippet": sensitive[:6] + "…" if len(sensitive) > 6 else sensitive, "description": pattern.get("description", "") }) ``` The metadata is optionally persisted: ```python def log_detection(log_path: str, channel: str | None, detections: list[dict], blocked: bool): """Append detection event to JSONL log.""" path = Path(log_path).expanduser() path.parent.mkdir(parents=True, exist_ok=True) entry = { "ts": datetime.datetime.utcnow().isoformat() + "Z", "channel": channel, "blocked": blocked, "detections": detections } with open(path, "a") as f: f.write(json.dumps(entry) + "\n") ``` ### Technical Analysis Each detection record copies up to the first six characters of the matched sensitive value. Values of six characters or fewer are retained in full. These snippets are always included in returned JSON and are also written to the JSONL audit file when detection logging is enabled. Credential prefixes can reveal provider, account, token type, environment, or other identifying information. Full retention of short matches creates a direct plaintext disclosure risk for custom patterns that detect short sensitive values. The log is opened using normal process umask behavior, without explicitly enforcing owner-only permissions. Existing files with permissive permissions ar ...[truncated 1231 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not store plaintext-derived snippets by default. Detection records should normally contain only: - Pattern name. - Action. - Non-sensitive description. - Timestamp and a random event identifier. 2. If correlation is required, use a keyed HMAC generated with a separately protected key rather than a raw prefix or ordinary unsalted hash. 3. Never retain short sensitive values in full. 4. Create new log files with owner-only permissions such as `0600`, and verify or correct permissions before appending. 5. Reject unsafe log targets where appropriate, including symbolic links and non-regular files, if the path can be influenced by an untrusted party. 6. Document the sensitivity and retention requirements of detection logs. 7. Add tests confirming that the original matched value and its plaintext prefix are absent from returned metadata and persisted logs. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (3)

Env Variable Harvesting

High
Category
Data Exfiltration
Content
"regex": r"(?i)(?:password|passwd|pass|pwd)\s*[=:\"'`\s]\s*(\S{6,})",
        "capture_group": 1,
        "action": "mask",
        "description": "Password in key=value form"
    },
    {
        "name": "database_url",
Confidence
80% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Credential Access

High
Category
Privilege Escalation
Content
"name": "env_file_line",
        "regex": r"(?m)^[A-Z_]{3,}=[^\s]{8,}$",
        "action": "mask",
        "description": ".env file variable assignment"
    },
    {
        "name": "sendgrid_key",
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill explicitly promotes logging detections for outgoing-message filtering but does not warn that logs may capture the very sensitive strings being detected, or adjacent message content and metadata. In a data-loss-prevention context, this omission is security-relevant because centralized logs often have broader retention and access than the original message path, turning leak prevention into secondary sensitive-data exposure.

Static analysis

No suspicious patterns detected.