Back to skill

Security audit

Neckr0ik Newsletter Manager

Security checks for vulnerabilities and agentic risk

Overview

This skill is not backed by evidence of malware, but it overstates real newsletter delivery and analytics while handling credentials and subscriber data with weak safeguards.

Install only for local drafting experiments, not production newsletter operations. Do not enter live API keys or import real subscriber lists unless the storage, redaction, and permissions issues are fixed. Treat send, schedule, web curation, and analytics output as simulated until real provider integrations and explicit approval safeguards are implemented.

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

Error
Location
scripts/newsletter.py:254
Finding
Arbitrary JSON File Modification Through Unsanitized Draft Identifier<![CDATA[ ## Vulnerability Details **File Location**: `scripts/newsletter.py:254-279` and `scripts/newsletter.py:287-310` **Vulnerability Type**: Path traversal and arbitrary file modification **Risk Level**: High ### Vulnerable Code ```python def schedule(self, draft_id: str, send_time: str, timezone: Optional[str] = None, optimal: bool = False) -> bool: """Schedule newsletter for delivery.""" draft_file = self.drafts_dir / f"{draft_id}.json" if not draft_file.exists(): print(f"✗ Draft not found: {draft_id}") return False # Load draft newsletter_data = json.loads(draft_file.read_text()) # Parse send time if optimal: # Calculate optimal send time (9 AM in recipient timezone) send_dt = datetime.now().replace(hour=9, minute=0, second=0, microsecond=0) if send_dt < datetime.now(): send_dt += timedelta(days=1) else: send_dt = datetime.fromisoformat(send_time.replace('Z', '+00:00')) # Update draft newsletter_data['scheduled_at'] = send_dt.timestamp() newsletter_data['status'] = 'scheduled' draft_file.write_text(json.dumps(newsletter_data, indent=2)) ``` The same flaw is present in the sending operation: ```python def send(self, draft_id: str, test_email: Optional[str] = None, segment: Optional[str] = None, platform: Optional[str] = None) -> bool: """Send newsletter.""" draft_file = self.drafts_dir / f"{draft_id}.json" if not draft_file.exists(): print(f"✗ Draft not found: {draft_id}") return False newsletter_data = json.loads(draft_file.read_text()) platform = platform or newsletter_data.get('platform', 'substack') print(f"✓ Sending: {draft_id}") print(f" Platform: {platform}") if test_email: print(f" Test mode: sending to {test_email}") # In real implementation, would send test email else: print(f" Recipients: All subscribers") # In real ...[truncated 2245 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate draft identifiers against the exact format produced by `_generate_id()`: ```python import re if not re.fullmatch(r"draft-[0-9]+-[0-9a-f]{8}", draft_id): raise ValueError("Invalid draft ID") ``` 2. Resolve and verify the final path before accessing it: ```python drafts_root = self.drafts_dir.resolve() draft_file = (drafts_root / f"{draft_id}.json").resolve() if draft_file.parent != drafts_root: raise ValueError("Draft path escapes the drafts directory") ``` 3. Explicitly reject absolute paths, path separators, `.` components, and `..` components. 4. Centralize safe draft lookup in one helper and use it in `schedule()`, `send()`, and any future draft operations. 5. Write updates atomically to a temporary file in the same protected directory and then replace the intended draft. 6. Add tests covering relative traversal, absolute paths, encoded separators, malformed identifiers, and symlink-related boundary cases. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/newsletter.py:103
Finding
Plaintext Storage and Terminal Disclosure of API Secrets and Subscriber PII<![CDATA[ ## Vulnerability Details **File Location**: `scripts/newsletter.py:103-108`, `scripts/newsletter.py:330-341`, and `scripts/newsletter.py:496-510` **Vulnerability Type**: Insecure sensitive-data storage and secret disclosure **Risk Level**: Medium ### Vulnerable Code Configuration values are serialized directly to a plaintext JSON file: ```python def _load_config(self) -> dict: """Load configuration.""" if self.config_file.exists(): return json.loads(self.config_file.read_text()) return {"platform": "substack", "default_style": "professional"} def _save_config(self, config: dict): """Save configuration.""" self.config_file.write_text(json.dumps(config, indent=2)) ``` Imported subscriber names and email addresses are also stored as plaintext JSON: ```python subscriber = Subscriber( email=email.strip(), name=name.strip() if name else None, ) # Save subscriber sub_file = self.subscribers_dir / f"{hashlib.md5(email.encode()).hexdigest()[:12]}.json" sub_file.write_text(json.dumps(subscriber.__dict__, indent=2)) count += 1 ``` Configuration values, including documented API secrets, are echoed to the terminal: ```python if args.action == 'set': keys = args.key.split('.') current = config for key in keys[:-1]: if key not in current: current[key] = {} current = current[key] current[keys[-1]] = args.value manager._save_config(config) print(f"✓ Set {args.key} = {args.value}") ``` The documentation specifically directs users to configure sensitive provider credentials: ```bash neckr0ik-newsletter-manager config set beehiiv.api_key <key> neckr0ik-newsletter-manager config set convertkit.api_key <key> neckr0ik-newsletter-manager config set convertkit.api_secret <secret> ``` ### Technical Analysis The application treats all configuration values identically, despite some documented keys being API keys or API secrets. These values are persisted in `config.json` without e ...[truncated 2246 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store provider credentials in an operating-system credential manager or dedicated secret-management service. Persist only a secret reference in `config.json`. 2. Support environment variables or protected standard-input prompts rather than requiring secrets in command-line arguments. 3. Never print secret values. Redact sensitive keys: ```python sensitive = {"api_key", "api_secret", "token", "password"} display = "[REDACTED]" if args.key.split(".")[-1] in sensitive else args.value print(f"✓ Set {args.key} = {display}") ``` 4. Create the configuration hierarchy with owner-only permissions, such as `0700` for directories and `0600` for files, and validate permissions when loading existing files. 5. Use atomic writes while preserving restrictive permissions. 6. Minimize stored subscriber fields, define a retention policy, and provide secure deletion and export controls. 7. Encrypt sensitive subscriber data at rest where the threat model requires protection from filesystem or backup disclosure. 8. Document that provider credentials must be narrowly scoped and rotated immediately after suspected exposure. ]]>

other

Warning
Location
scripts/newsletter.py:287
Finding
Newsletter Delivery and Analytics Falsely Report Success Without Provider Operations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/newsletter.py:287-312` and `scripts/newsletter.py:348-381` **Vulnerability Type**: Deceptive operational behavior and integrity failure **Risk Level**: Medium ### Vulnerable Code The sending operation performs no platform API call but still records and reports successful delivery: ```python def send(self, draft_id: str, test_email: Optional[str] = None, segment: Optional[str] = None, platform: Optional[str] = None) -> bool: """Send newsletter.""" draft_file = self.drafts_dir / f"{draft_id}.json" if not draft_file.exists(): print(f"✗ Draft not found: {draft_id}") return False newsletter_data = json.loads(draft_file.read_text()) platform = platform or newsletter_data.get('platform', 'substack') print(f"✓ Sending: {draft_id}") print(f" Platform: {platform}") if test_email: print(f" Test mode: sending to {test_email}") # In real implementation, would send test email else: print(f" Recipients: All subscribers") # In real implementation, would send to platform API # Mark as sent newsletter_data['sent_at'] = time.time() newsletter_data['status'] = 'sent' draft_file.write_text(json.dumps(newsletter_data, indent=2)) print(f"✓ Sent successfully") return True ``` Analytics are generated from fixed percentages instead of provider observations: ```python def get_analytics(self, period: int = 30, newsletter_id: Optional[str] = None) -> dict: """Get newsletter analytics.""" # In real implementation, would query platform APIs # For now, return simulated analytics analytics = { "period_days": period, "total_sent": 0, "total_opens": 0, "total_clicks": 0, "open_rate": 0.0, "click_rate": 0.0, "subscribers": 0, "growth": 0.0, } # Count subscribers for _ in self.subscribers_dir.g ...[truncated 2793 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Until provider integration exists, make `send()` fail explicitly with a nonzero exit status and a clear “not implemented” message. 2. Do not set `status` to `sent` until a provider returns an authenticated delivery or accepted-message identifier. 3. Record separate states such as `pending`, `submitted`, `accepted`, `delivered`, and `failed`, with provider response metadata. 4. Implement platform-specific API clients with authentication, request timeouts, retry limits, idempotency keys, and structured error handling. 5. Implement scheduling through a real scheduler or provider API, and expose scheduling failures to the caller. 6. Either remove simulated analytics from production commands or clearly label every value as demonstration data. 7. Retrieve analytics from authenticated provider endpoints and retain source timestamps and provider identifiers for traceability. 8. Add end-to-end tests that verify a provider request occurs before success is recorded and that provider failures cannot result in a `sent` state. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented behavior overstates real integration and sending capabilities, creating a description-behavior mismatch. This is dangerous because users may trust the skill to perform real newsletter delivery or platform operations, causing unsafe operational assumptions, accidental disclosure of subscriber data to unsupported paths, or secret entry into a tool that cannot securely use them.

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
s:
                    continue
                drafts.append(data)
            except json.JSONDecodeError:
                continue
        
        # Sort by creation time, newest first
        drafts.sort(key=lambda x: x.get('created_at', 0), reverse=True)
        
        return drafts


def main():
    """CLI entry point."""
    
    import argparse
    
    parser = argparse.ArgumentParser(description="Newsletter Manager")
    subparsers = parser.add_subparsers(dest='command', help='Command')
    
    # create command
    create_parser = subparsers.add_parser('create', help='Create newsletter draft')
    create_parser.add_argument('--topic', required=True, help='Newsletter topic')
    create_parser.add_argument('--style', default='professional', 
                                choices=['professional', 'casual', 'technical', 'newsy'])
    create_parser.add_argument('--length', type=int, default=500)
    create_parser.add_argument('--curate', action='store_true', help='Curate lin
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises operational capabilities such as file access, writing, and shell-like execution patterns, but it does not declare any explicit tool scope or permissions boundary. In an agent ecosystem, this weakens reviewability and can lead to over-privileged execution, making it easier for the skill to access local files or perform unintended actions without informed user consent.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill explicitly promotes sending to all subscribers with no caution about bulk outbound impact, consent, rate limits, or accidental mass delivery. In a newsletter-management context, this increases the risk of spam, reputational damage, compliance violations, and unintended large-scale actions triggered by an agent or user mistake.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Importing and exporting subscriber lists inherently involves personal data, yet the skill provides no warning about privacy, secure storage, minimization, or lawful handling. In this context, that omission is risky because newsletter subscriber data commonly includes email addresses and segmentation metadata that could be exposed, mishandled, or exported insecurely.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The setup instructions ask users to enter API keys and secrets without any warning that these credentials are highly sensitive. This is particularly dangerous in an AI-agent skill because users may provide live production credentials into insecure contexts, leading to account takeover, unauthorized newsletter sends, subscriber data access, or abuse of connected platforms.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The skill advertises support for multiple newsletter platforms, but the platform definitions are only static metadata and are not backed by implemented API behavior. In an agent context, this mismatch can cause unsafe reliance on nonexistent integrations, leading to silent failures or incorrect automation decisions.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
            # Call ollama
            result = subprocess.run(
                ["ollama", "run", "llama3.2:latest", prompt],
                capture_output=True,
                text=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:
            # Search for recent content on topic
            result = subprocess.run(
                ["ollama", "run", "llama3.2:latest", 
                 f"List 5 recent and important news items about {topic}. Format as JSON array with title and url fields."],
                capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The send method claims successful delivery and marks drafts as sent even though it does not contact any newsletter platform API. This can mislead operators into believing communications were delivered, causing business/process integrity failures and potentially unsafe downstream automation based on false send state.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The import routine writes subscriber email addresses and names to disk as plaintext JSON files without warning, consent handling, retention controls, or access protections. In a newsletter-management skill, this materially increases privacy and compliance risk because it stores personal data in a predictable local directory.

Vague Triggers

Low
Confidence
87% confidence
Finding
The description advertises broad autonomous newsletter management capabilities without clear trigger boundaries, approval requirements, or platform-specific safety constraints. In an agent ecosystem, this can cause over-activation or misuse for mass content generation and sending, especially because the skill implies end-to-end automation across multiple email platforms.

Intent-Code Divergence

Low
Confidence
93% confidence
Finding
The _curate_links docstring says it curates relevant links from web search, which implies retrieving current web results. The implementation instead invokes `ollama run` with a prompt asking for recent items and then extracts lines containing 'http', without performing any actual web search.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The method name and docstring present this as getting newsletter analytics, which suggests real measured campaign data. The implementation only counts local subscriber files and fabricates open, click, and growth metrics using fixed percentages.

Static analysis

No suspicious patterns detected.