Back to skill

Security audit

Crm

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent local CRM tool, but it persists personal contact data into searchable agent memory and imports untrusted contact files without enough safeguards.

Install only if you are comfortable keeping contact details and notes in local searchable memory. Use dry-run previews for imports, avoid importing untrusted contact files without reviewing them, do not store secrets or highly sensitive personal details, and prefer exporting to a dedicated CRM export folder rather than arbitrary home or shared directories.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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)

T02 · Agent Memory Poisoning

Error
Location
scripts/crm-import.py:145
Finding

Untrusted CRM data can poison persistent agent memory and heartbeat output

Content
View full analysis
tuple[Path, bool]: """Create a contact markdown file. Returns (path, created).""" name = contact.get("name", "Unknown") slug = slugify(name) filepath = PEOPLE_DIR / f"{slug}.md" if filepath.exists() and not overwrite: return filepath, False today = datetime.now().strftime("%Y-%m-%d") # Build frontmatter lines = [ "---", f"name: {name}", "type: person", ] # Tags tags = contact.get("tags", "") if tags: if isinstance(tags, str): tags = [t.strip() for t in tags.split(",") if t.strip()] lines.append(f"tags: [{', '.join(tags)}]") else: lines.append("tags: [imported]") # Other fields field_map = { "company": "company", "role": "role", "email": "email", "phone": "phone", "telegram": "telegram", "twitter": "twitter", "linkedin": "linkedin", "website": "website", "location": "location", } for key, yaml_key in field_map.items(): value = contact.get(key) if value: # Escape quotes in values if ":" in str(value) or '"' in str(value): value = f'"{value}"' lines.append(f"{yaml_key}: {value}") lines.extend([ f"first_contact: {today}", f"last_contact: {today}", "status: active", "---", "", f"# {name}", "", "## Context", "", "", "## Interactions", "", "", " ...[truncated 3952 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/crm-export.py:83
Finding

CSV exports permit spreadsheet formula injection

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/crm-export.py:199
Finding

Export path validation uses unsafe string-prefix containment checks

Content
View full analysis
Path: """Ensure output path is safe (not escaping workspace).""" resolved = path.resolve() # Allow writing to current directory or subdirectories cwd = Path.cwd().resolve() if not str(resolved).startswith(str(cwd)) and not str(resolved).startswith("/tmp"): # Also allow absolute paths within home home = Path.home().resolve() if not str(resolved).startswith(str(home)): raise ValueError(f"Output path must be within workspace or home: {path}") return resolved ``` The validated path is subsequently opened with truncation or passed to `write_text()`, overwriting an existing file: ```python with open(output_path, "w", newline="", encoding="utf-8") as f: ``` ### Technical Analysis Filesystem containment is checked by comparing string prefixes. A path sharing the textual prefix of an allowed directory is not necessarily inside that directory. For example, if the working directory is `/work/project`, `/work/project-evil/output.csv` passes `startswith("/work/project")` even though it is a sibling directory. The same flaw applies to the home-directory comparison. The `/tmp` test also accepts every resolved path beginning with those characters rather than verifying membership beneath the `/tmp` directory. Additionally, the function intentionally permits any destination under the user's home directory. This is substantially broader than the comment and function name imply and allows truncation of arbitrary user-owned files when a chosen output path is accepted. `Path.resolve()` mitigates ordinary `..` traversal and resolves existing symlinks, but it does not correct the string-prefix containment flaw or the excessively broad home-direct ...[truncated 1259 chars]
Remediation
View remediation
Path: candidate = path.resolve() if not candidate.is_relative_to(EXPORT_ROOT): raise ValueError("Output path must be inside the CRM export directory") return candidate ``` 2. For Python versions without `Path.is_relative_to()`, use: ```python try: candidate.relative_to(EXPORT_ROOT) except ValueError: raise ValueError("Output path escapes the export directory") ``` 3. Remove the blanket allowances for `/tmp` and the user's entire home directory. If temporary output is needed, create a dedicated directory with `tempfile.TemporaryDirectory()`. 4. Refuse to overwrite existing files by default. Require an explicit `--overwrite` option and display the fully resolved destination before writing. 5. Create parent directories securely and verify that the final parent remains under the approved root immediately before opening the file. 6. Where supported, use exclusive creation (`"x"` mode) for new exports and mitigate symlink races with descriptor-based APIs and `O_NOFOLLOW`. 7. Add tests for sibling-prefix paths such as `/allowed-root-evil`, `..` traversal, symlinks, absolute home paths, and existing destination files. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README explicitly encourages natural-language ingestion of contact details and states that files are stored under memory/ for semantic search, but it does not warn that this can place sensitive personal data into a broadly searchable knowledge store. In a CRM context, users may include emails, phone numbers, handles, locations, relationship notes, and other personal context, so omission of privacy and data-handling guidance creates a real risk of over-collection, unintended retention, and disclosure through search or downstream tooling.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs the agent to read, write, and execute shell-based workflows but does not declare any explicit tool scope or permission boundaries. That increases the chance of unintended file modification or command execution when the skill is auto-invoked, especially because it operates on user data under memory/ and includes import/export and indexing steps.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The activation description is broad enough to match many normal conversations about contacts, networking, reminders, and relationship management, which can cause the skill to trigger in situations where the user did not intend file operations. In this skill, overbroad activation is more concerning because activation can lead to persistence of personal data and command execution through the provided scripts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill supports importing and exporting contact records containing personal data but does not require a user-facing warning or confirmation before reading sensitive files or writing data to new locations. That creates privacy and data-handling risk, including accidental ingestion of sensitive contact lists or export of PII into broadly accessible files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code writes potentially sensitive CRM contact information, including names, email addresses, phone numbers, locations, and tags, to CSV, vCard, or Markdown files. Although the script prints a success message after export, it does not provide any user-facing warning or disclosure before performing the file write about the sensitivity of the exported data or the privacy implications of creating portable contact files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script creates directories and writes one file per contact, which can modify or overwrite local CRM data when run without --dry-run. Although the tool's purpose is importing contacts, the code lacks a stronger user disclosure near the write path about persistent filesystem changes, especially since overwrite behavior is optional and imports may be bulk operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This tool stores personal contact data such as names, emails, phone numbers, social handles, and relationship notes to local markdown files without any explicit notice, consent reminder, or privacy warning at write time. In a CRM skill, persistent storage is expected, but silent persistence of personal data can still create privacy and compliance risk if users do not realize where the data is being written or how broadly it may be accessible on disk or in version control.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/crm-add.py (reported line 181)May include surrounding context.

python
"tags", "location", "introduced_by", "met_at", "follow_up", "status"]:
        # Handle hyphens in arg names
        arg_name = field.replace("_", "-")
        value = getattr(args, field.replace("-", "_"), None)
        if value:
            fields[field] = value

Static analysis

No suspicious patterns detected.