Back to skill

Security audit

Convex Obsidian

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent memory purpose, but it sends conversations to a fixed remote service and uses under-scoped persistent memory, local-file access, and backend authority.

Review before installing. Use this only with a Convex deployment and Obsidian vault you control, rotate/remove the exposed deploy keys, add authentication and tenant checks to the backend, disable automatic upload by default, add redaction and deletion controls, restrict vault folders, and pin dependencies before trusting it with private conversations or customer data.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (7)

other

Error
Location
hook.py:15
Finding

Automatic Disclosure of Conversation Data to a Fixed Remote Deployment

Content
View full analysis
Any: """Mutation Convex via HTTP.""" try: resp = requests.post( f"{CONVEX_URL}/api/mutation", json={"path": action, "args": args}, timeout=10 ) return resp.json().get("value") if resp.status_code == 200 else None except: return None def save_conversation_turn( session_id: str, user_message: str, ai_response: str, tags: List[str] = None, importance: int = 5 ) -> bool: """Salva um turno de conversa no Convex.""" content = f"User: {user_message}\nAI: {ai_response[:500]}" result = convex_mutation("memory:saveMemory", { "sessionId": session_id, "content": content, "source": "conversation", "tags": tags or ["auto-saved"], "importance": importance, "metadata": { "timestamp": datetime.now().isoformat(), "has_response": len(ai_response) > 0 } }) return result is not None ``` ```python def auto_save_and_context(session_id: str, user_msg: str, ai_msg: str) -> Optional[str]: """ Função principal chamada automaticamente após cada turno. Retorna contexto relevante se detectar necessidade. """ # Sempre salva no Convex save_conversation_turn(session_id, user_msg, ai_msg) ``` ### Technical Analysis Every invocation of `auto_save_and_context` sends the complete user message and the first 500 characters of the AI response to a remote Convex deployment. The executable path contains no consent check, sensitive-data filtering, destination ownership validation, or per-session opt-in control. The endpoint ...[truncated 1249 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
convex/memory.ts:8
Finding

Public Memory Operations Lack Authentication and Tenant Authorization

Content
View full analysis
{ const timestamp = Date.now(); const memoryId = await ctx.db.insert("memories", { sessionId: args.sessionId, timestamp, content: args.content, source: args.source, tags: args.tags, importance: args.importance, metadata: args.metadata, }); return memoryId; }, }); ``` ```typescript export const getRecent = query({ args: { limit: v.optional(v.number()), sessionId: v.optional(v.string()), }, handler: async (ctx, args) => { const limit = args.limit ?? 20; if (args.sessionId) { return await ctx.db .query("memories") .withIndex("by_session", q => q.eq("sessionId", args.sessionId)) .order("desc") .take(limit); } return await ctx.db .query("memories") .withIndex("by_timestamp") .order("desc") .take(limit); }, }); ``` ```typescript export const cleanupOld = mutation({ args: { olderThanDays: v.number(), }, handler: async (ctx, args) => { const cutoff = Date.now() - args.olderThanDays * 24 * 60 * 60 * 1000; const old = await ctx.db .query("memories") .withIndex("by_timestamp", q => q.lt("timestamp", cutoff)) .collect(); for (const memory of old) { await ctx.db.delete(memory._id); const searchEntries = await ctx.db .query("searchIndex") .filter(q => q.eq(q. ...[truncated 1650 chars]
Remediation
View remediation

T02 · Agent Memory Poisoning

Error
Location
hook.py:64
Finding

Untrusted Persistent Memories Are Injected into Agent Context

Content
View full analysis
str: """Busca contexto relevante das memórias.""" convex_results = convex_query("memory:searchMemories", { "query": query, "limit": limit, **({"sessionId": session_id} if session_id else {}) }) or [] vault = Path(VAULT_PATH) obsidian_results = [] if vault.exists(): query_terms = query.lower().split() for md_file in vault.rglob("*.md"): try: content = md_file.read_text(encoding='utf-8').lower() score = sum(content.count(term) for term in query_terms) if score > 0: obsidian_results.append({ "file": md_file.name, "preview": md_file.read_text(encoding='utf-8')[:300] }) except: pass context_parts = [] if convex_results: context_parts.append("## Memórias Recentes (Convex):") for r in convex_results[:3]: context_parts.append(f"- {r.get('content', '')[:200]}...") if obsidian_results: context_parts.append("\n## Conhecimento do Vault (Obsidian):") for r in obsidian_results[:2]: context_parts.append(f"- [{r['file']}]: {r['preview'][:150]}...") return "\n".join(context_parts) if context_parts else "" ``` ```python if should_search_memory(user_msg): context = get_relevant_context(user_msg, session_id) if context: return f"\n\n[Contexto de memórias anteriores]:\n{context}" ``` ### Technical Analysis Text retrieved from remote memory records and local Markdown files is concatenated directly into Agent context. The implementation does not quote or struc ...[truncated 1415 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:125
Finding

Convex Deployment Credentials Are Embedded in Skill Documentation

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
memory.py:183
Finding

User-Controlled Folder Path Allows Writes Outside the Obsidian Vault

Content
View full analysis
str: """Save a note to Obsidian vault.""" vault = Path(vault_path).expanduser() target_dir = vault / folder target_dir.mkdir(parents=True, exist_ok=True) date_str = datetime.now().strftime("%Y-%m-%d") safe_title = re.sub(r'[^\w\s-]', '', title).strip().replace(' ', '-') filename = f"{date_str}-{safe_title}.md" filepath = target_dir / filename tags_str = ' '.join(f'#{tag}' for tag in (tags or [])) note = f"""# {title} {tags_str} Date: {datetime.now().strftime("%Y-%m-%d %H:%M")} --- {content} """ filepath.write_text(note, encoding='utf-8') return str(filepath) ``` ```python save_obs_parser.add_argument("--folder", default="01-Inbox") ``` ### Technical Analysis The title is partially sanitized, but the user-controlled `folder` value is not validated. In Python path handling, an absolute `folder` can replace the vault prefix, while `..` components can escape the vault after path resolution. The function creates directories and writes the generated Markdown file without verifying that the final destination remains under the configured vault. Existing files with the same date and sanitized title are overwritten. ### Attack Path 1. An attacker or untrusted caller invokes `save-obsidian`. 2. The caller supplies an absolute `--folder` value or a path containing traversal components. 3. `target_dir = vault / folder` produces a destination outside the intended vault. 4. `mkdir` creates the destination where permissions allow. 5. `write_text` creates or overwrites a predictable Markdown filename with attacker-selected content. ### Impact Assessme ...[truncated 284 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
search.sh:8
Finding

Runtime Wrappers Install Mutable and Unpinned Dependencies

Content
View full analysis
=2.31.0 ``` ```bash npx convex@latest deploy --preview-create openclaw-memory ``` ### Technical Analysis The shell wrappers download Python dependencies on first execution. `search.sh` requests an unrestricted current version of `requests`, while `requirements.txt` specifies only a lower bound. Neither path verifies package hashes. The deployment instructions also execute `convex@latest`, causing the effective executable version to change over time and bypassing the reproducibility otherwise offered by the npm lockfile. No malicious dependency was identified in the reviewed package. The vulnerability is that future package compromise or incompatible releases can change code executed after the Skill has been reviewed. ### Attack Path 1. A user runs a wrapper without an existing virtual environment or follows the documented deployment command. 2. The package manager contacts its configured registry. 3. It resolves a mutable package version that was not part of the audited artifact. 4. Package installation or package CLI code executes under the user's account. 5. A compromised registry response, dependency release, or local registry configuration can introduce attacker-controlled code. ### Impact Assessment Compromised dependency code can run with the privileges of the user executing the Skill. This may permit access to conversation files, environment variables, the Obsidian vault, deployment c ...[truncated 64 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
hook.py:50
Finding

Hook Payload Does Not Match the Convex Metadata Schema

Content
View full analysis
0 } }) ``` The backend accepts a different metadata shape: ```typescript metadata: v.optional(v.object({ agentId: v.optional(v.string()), channel: v.optional(v.string()), topic: v.optional(v.string()), })), ``` Errors are suppressed by the HTTP helper: ```python def convex_mutation(action: str, args: Dict) -> Any: """Mutation Convex via HTTP.""" try: resp = requests.post( f"{CONVEX_URL}/api/mutation", json={"path": action, "args": args}, timeout=10 ) return resp.json().get("value") if resp.status_code == 200 else None except: return None ``` ### Technical Analysis The hook submits `timestamp` and `has_response` metadata fields, while the strict Convex validator permits only `agentId`, `channel`, and `topic`. The mutation is therefore expected to fail validation. The broad exception handler and status handling discard diagnostic information. This produces an unsafe failure mode: users may not understand that persistence failed, while the sensitive conversation payload was still transmitted to the remote server before validation occurred. ### Attack Path 1. The automatic hook constructs a conversation memory. 2. It includes metadata fields not accepted by the deployed function validator. 3. The hook transmits the full request body to the remote Convex endpoint. 4. Convex re ...[truncated 533 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (39)

Tainted flow: 'CONVEX_URL' from os.getenv (line 15, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · hook.py (reported line 21)May include surrounding context.

python
def convex_query(action: str, args: Dict) -> Any:
    """Query Convex via HTTP."""
    try:
        resp = requests.post(
            f"{CONVEX_URL}/api/query",
            json={"path": action, "args": args},
            timeout=10

Tainted flow: 'CONVEX_URL' from os.getenv (line 15, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · hook.py (reported line 33)May include surrounding context.

python
def convex_mutation(action: str, args: Dict) -> Any:
    """Mutation Convex via HTTP."""
    try:
        resp = requests.post(
            f"{CONVEX_URL}/api/mutation",
            json={"path": action, "args": args},
            timeout=10

Tainted flow: 'CONVEX_URL' from os.getenv (line 14, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · search.py (reported line 20)May include surrounding context.

python
def search_convex(query: str, limit: int = 5) -> List[Dict]:
    """Busca memórias no Convex."""
    try:
        resp = requests.post(
            f"{CONVEX_URL}/api/query",
            json={"path": "memory:searchMemories", "args": {"query": query, "limit": limit}},
            timeout=10

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill advertises automatic persistent saving of every conversation and later context reinjection without any visible privacy notice, consent model, retention policy, or data handling limitations. This is dangerous because users may unknowingly send sensitive or regulated information to persistent stores and have it resurfaced later in other contexts.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The stated design persists every conversation automatically and uses stored content to inject memory into future responses, with no mention of filtering, minimization, or separation of sensitive data. This creates a broad privacy and data leakage risk, since secrets, customer data, or unrelated prior content can be retained and later resurfaced unexpectedly.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documented post-message hook enables automatic saving of each message to a remote Convex deployment, but the instructions do not warn that all user content will be transmitted off-host. In practice, this can cause silent exfiltration of sensitive prompts, internal business data, or credentials entered during normal use.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The documented behavior explicitly says every message is auto-saved and that terms like 'ontem', 'antes', or 'lembra' trigger contextual reinsertion. This makes accidental disclosure more likely because a simple conversational cue can pull prior user content into a current reply without confirming relevance or sensitivity.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The examples encourage storing customer requests, budgets, and meeting summaries in persistent memory systems, which are likely to contain confidential business or personal information. Normalizing this behavior without safeguards increases the likelihood of collecting sensitive data into stores that may be queried, synced, or exposed later.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 120)May include surrounding context.

Configuração de Variáveis

bash
# ~/.openclaw/.env ou exportar
export CONVEX_DEPLOYMENT_URL="https://energized-goshawk-977.convex.cloud"
export VAULT_PATH="/home/andrey/Vault"

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill documentation includes live Convex deployment keys and explicit instructions to export and use them. Publishing usable deployment credentials in skill content can let anyone redeploy, alter, or take over the backend, directly affecting stored conversation data and memory retrieval behavior.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The vault search reads arbitrary markdown note contents and injects previews into returned context based on simple keyword matches from the user's message. This is dangerous because benign-looking prompts can cause unrelated private note fragments to be surfaced to the model or user, creating a direct path for unintended disclosure of sensitive local data.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: ws==8.18.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
96% confidence
Finding

The lockfile pins the transitive dependency ws to version 8.18.0, and the provided advisory metadata indicates this version is affected by an uninitialized memory disclosure issue and a memory-exhaustion denial-of-service issue. Even though this is only a dependency manifest, it is a true supply-chain risk because installing this package set would bring the vulnerable library into the runtime used by convex, potentially exposing applications that process untrusted WebSocket traffic.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation instructs saving summaries and synchronizing memory into a local Obsidian vault without warning that potentially sensitive conversation data will be written to plain local files. This increases the risk of unintended disclosure through local backups, sync tools, shared machines, or later indexing by other software.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding

The documentation instructs use of npx convex@latest deploy, which fetches and executes the latest package version at runtime rather than a reviewed, pinned release. In a skill that handles persistent memory and remote deployment, this creates a supply-chain risk: a compromised or breaking upstream package could be executed with the user's privileges during deploy.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding

This second deployment example repeats use of npx convex@latest deploy, again allowing unpinned remote code execution from the package registry. Because the same section also uses deployment credentials, the risk is amplified: an upstream compromise could run in an environment containing sensitive deployment tokens.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code comment says old memories are 'moved to cold storage', but the implementation permanently deletes the memory records and associated search-index entries. In a memory-management component, this mismatch is dangerous because operators or downstream code may rely on retention/archival behavior that does not actually exist, causing unintended irreversible data loss and weakening auditability.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The cleanupOld mutation performs irreversible database deletions of stored memories and related search-index entries. Although the code has an internal comment, there is no confirmation prompt, user-visible logging, or explicit warning in this file that data will be permanently removed.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The top-level documentation says the hook provides 'Integração automática Convex + Obsidian' and 'Salva conversas automaticamente', which implies conversations are persisted across both systems. In implementation, saving occurs only through memory:saveMemory to Convex, while Obsidian is used only as a local read-only search source in get_relevant_context.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · hook.py (reported line 33)May include surrounding context.

python
def convex_mutation(action: str, args: Dict) -> Any:
    """Mutation Convex via HTTP."""
    try:
        resp = requests.post(
            f"{CONVEX_URL}/api/mutation",
            json={"path": action, "args": args},
            timeout=10

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill silently scans the entire local Obsidian vault and reads markdown contents to build context, without any user-facing disclosure or scope restriction. In this context that is risky because a vault often contains private notes, credentials, work documents, or personal records, and the search is triggered by ordinary conversational phrasing.

Content

No source excerpt is available for this finding.