Back to skill

Security audit

Convex Backend

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned, but its deployed memory and log functions lack clear access controls and its setup runs unpinned tools with deployment credentials.

Install only if you are comfortable reviewing and hardening the Convex deployment first. Pin Convex CLI and mcporter versions, avoid inline deploy keys, add authentication and per-agent or per-tenant authorization to every memory/log function before multi-agent or shared use, and treat stored memory/log content as sensitive untrusted data that may need deletion and retention controls.

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

T02 · Agent Memory Poisoning

Error
Location
convex/memory.js:6
Finding
Unauthenticated Public Functions Allow Cross-Agent Memory and Daily-Log Access<![CDATA[ ## Vulnerability Details **File Location**: `convex/memory.js:6-52`, `convex/memory.js:68-109`; `convex/components/openclawBackend/memory.js:5-92`, `convex/components/openclawBackend/memory.js:107-197` **Vulnerability Type**: Broken access control, cross-tenant data access, and persistent memory poisoning **Risk Level**: High ### Vulnerable Code ```js export const addMemory = mutation({ args: { agentId: v.string(), type: v.union( v.literal("fact"), v.literal("preference"), v.literal("decision"), v.literal("note"), ), content: v.string(), tags: v.optional(v.array(v.string())), }, returns: v.string(), handler: async (ctx, args) => { return await ctx.runMutation(components.openclawBackend.memory.addMemory, args); }, }); export const searchMemory = query({ args: { agentId: v.string(), type: v.optional( v.union( v.literal("fact"), v.literal("preference"), v.literal("decision"), v.literal("note"), ), ), limit: v.optional(v.number()), }, returns: v.array( v.object({ _id: v.string(), type: v.union( v.literal("fact"), v.literal("preference"), v.literal("decision"), v.literal("note"), ), content: v.string(), tags: v.optional(v.array(v.string())), createdAt: v.number(), }), ), handler: async (ctx, args) => { return await ctx.runQuery(components.openclawBackend.memory.searchMemory, args); }, }); ``` The same caller-controlled identity pattern is used by the daily-log functions: ```js export const writeDailyLog = mutation({ args: { agentId: v.string(), date: v.string(), content: v.string(), }, returns: v.string(), handler: async (ctx, args) => { return await ctx.runMutation(components.openclawBackend.memory.writeDailyLog, args); }, }); export const getDailyLog = query({ args: { agentId: v.string(), date: v.string(), ...[truncated 3413 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication in every public query and mutation: ```js const identity = await ctx.auth.getUserIdentity(); if (!identity) { throw new Error("Unauthenticated"); } ``` 2. Do not use a caller-supplied `agentId` as proof of ownership. Derive the tenant and permitted agent scope from the authenticated identity or a server-maintained authorization mapping. 3. If callers must provide an `agentId`, verify that the authenticated principal is explicitly authorized to access that agent before forwarding the request. 4. Store a tenant or owner identifier with each memory and daily-log record and include it in indexes and authorization checks. 5. Apply the same authorization policy to add, search, delete, write, get, and list operations. 6. Keep component functions internal where possible and expose only authenticated root wrappers. 7. Treat retrieved memory and log content as untrusted data. Delimit it from system instructions and ensure the agent is explicitly instructed not to execute directives found in stored content. 8. Add tests proving that one authenticated principal cannot read or modify another principal's records and that unauthenticated calls are rejected. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
convex/components/openclawBackend/memory.js:94
Finding
Memory Deletion Is Authorized Only by Possession of a Record Identifier<![CDATA[ ## Vulnerability Details **File Location**: `convex/memory.js:56-64`; `convex/components/openclawBackend/memory.js:94-104` **Vulnerability Type**: Insecure direct object reference and missing ownership validation **Risk Level**: High ### Vulnerable Code Public wrapper: ```js export const deleteMemory = mutation({ args: { memoryId: v.string(), }, returns: v.boolean(), handler: async (ctx, args) => { return await ctx.runMutation(components.openclawBackend.memory.deleteMemory, args); }, }); ``` Component implementation: ```js export const deleteMemory = mutation({ args: { memoryId: v.id("agentMemory"), }, returns: v.boolean(), handler: async (ctx, args) => { const entry = await ctx.db.get(args.memoryId); if (!entry) return false; await ctx.db.delete(args.memoryId); return true; }, }); ``` ### Technical Analysis The public mutation accepts a record identifier without requiring an authenticated identity, agent identifier, tenant identifier, or authorization context. The component verifies only that the referenced record exists and then deletes it. Possession of a Convex document ID is therefore sufficient to delete the corresponding memory. This is an insecure direct object reference. The companion `searchMemory` query returns `_id` values and is also missing authorization, providing a direct way to obtain deletion targets. ### Attack Path 1. The attacker calls `memory:searchMemory` with a victim's `agentId`. 2. The search response reveals memory `_id` values. 3. The attacker submits one of these identifiers to `memory:deleteMemory`. 4. The public wrapper forwards the identifier to the component. 5. The component retrieves and deletes the record without checking the caller's identity or comparing ownership against `entry.agentId`. 6. The victim's persistent memory is permanently removed. If a record ID is obtained through another disclosure channel, the deletion step can be performed independently o ...[truncated 532 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Authenticate the caller before permitting deletion. 2. Retrieve the target record and verify that its owner or tenant matches the authenticated principal: ```js const identity = await ctx.auth.getUserIdentity(); if (!identity) throw new Error("Unauthenticated"); const entry = await ctx.db.get(args.memoryId); if (!entry) return false; if (!isAuthorized(identity, entry.agentId)) { throw new Error("Forbidden"); } await ctx.db.delete(args.memoryId); return true; ``` 3. Derive permitted agent scope server-side rather than accepting ownership claims from request parameters. 4. Make the component deletion mutation internal if it should only be called through a validated root function. 5. Consider soft deletion, audit logging, and recovery controls for persistent agent state. 6. Add authorization tests for deletion attempts against records owned by another agent or tenant. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
convex/components/openclawBackend/memory.js:55
Finding
Unbounded Query Limits Permit Resource-Exhaustion Requests<![CDATA[ ## Vulnerability Details **File Location**: `convex/components/openclawBackend/memory.js:55-84`, `convex/components/openclawBackend/memory.js:181-190` **Vulnerability Type**: Missing input bounds and denial-of-service exposure **Risk Level**: Medium ### Vulnerable Code Memory search: ```js handler: async (ctx, args) => { const maxResults = args.limit ?? 50; if (args.type) { const entries = await ctx.db .query("agentMemory") .withIndex("by_agentId_and_type", (q) => q.eq("agentId", args.agentId).eq("type", args.type), ) .order("desc") .take(maxResults); return entries.map((entry) => ({ _id: entry._id, type: entry.type, content: entry.content, tags: entry.tags, createdAt: entry.createdAt, })); } const entries = await ctx.db .query("agentMemory") .withIndex("by_agentId", (q) => q.eq("agentId", args.agentId)) .order("desc") .take(maxResults); return entries.map((entry) => ({ _id: entry._id, type: entry.type, content: entry.content, tags: entry.tags, createdAt: entry.createdAt, })); }, ``` Daily-log listing: ```js handler: async (ctx, args) => { const maxResults = args.limit ?? 30; const entries = await ctx.db .query("agentDailyLogs") .withIndex("by_agentId", (q) => q.eq("agentId", args.agentId)) .order("desc") .take(maxResults); return entries.map((entry) => ({ date: entry.date, contentPreview: entry.content.slice(0, 200), updatedAt: entry.updatedAt, })); }, ``` ### Technical Analysis The `limit` argument is validated only as a generic number. The code supplies defaults but does not enforce that caller-provided values are finite, positive integers below a safe maximum. The value is passed directly to `.take()`. A caller can request a result set much larger than the intended defaults. Repeated large requests can increase database reads, response serialization, network bandwidth, execu ...[truncated 1032 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a small positive integer range before querying: ```js const requestedLimit = args.limit ?? 50; if (!Number.isInteger(requestedLimit) || requestedLimit < 1) { throw new Error("limit must be a positive integer"); } const maxResults = Math.min(requestedLimit, 100); ``` 2. Use separate, appropriately sized caps for memory searches and daily-log lists. 3. Add authentication and authorization before performing database reads. 4. Apply per-principal rate limits or quotas to repetitive queries. 5. Prefer cursor-based pagination for larger datasets rather than permitting arbitrarily large single responses. 6. Add tests for zero, negative, fractional, non-finite, and excessively large limits. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:79
Finding
Mutable Unpinned Packages Are Downloaded and Executed with Deployment Credentials<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:79-85`, `SKILL.md:99-106`, `SKILL.md:150`, `SKILL.md:169`, `SKILL.md:326-334` **Vulnerability Type**: Unsafe runtime dependency retrieval and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code Recommended MCP configuration: ```json { "mcpServers": { "convex": { "command": "npx", "args": ["-y", "convex@latest", "mcp", "start"] } } } ``` Preflight commands: ```bash mcporter --version || npx -y mcporter --version mcporter list || npx -y mcporter list mcporter list convex --schema || npx -y mcporter list convex --schema ``` Deployment command: ```bash cd /home/node/.openclaw/skills/convex-backend CONVEX_DEPLOY_KEY=... npx -y convex@latest deploy ``` Runtime bridge example: ```bash npx -y mcporter call convex.run --args '{ "functionName": "memory:addMemory", "args": { "agentId": "<your-agent-id>", "type": "fact", "content": "User prefers TypeScript over JavaScript for all new projects", "tags": ["preferences", "coding"] } }' ``` ### Technical Analysis The instructions repeatedly use `npx -y` to download and execute packages without interactive confirmation. `convex@latest` resolves to a mutable release, while `mcporter` is invoked without any version constraint. The effective executable code can therefore change after this skill package has been reviewed. A compromised registry account, malicious upstream release, dependency compromise, or unexpected breaking update could cause arbitrary package code to run locally. The deployment command is especially sensitive because the executed package operates while `CONVEX_DEPLOY_KEY` is present in the process environment. The MCP server and bridge may also execute with access to other runtime credentials and agent capabilities. ### Attack Path 1. An attacker compromises an upstream package, maintainer account, publishing token, transitive dependency, or registry resolution path. 2. A malic ...[truncated 1087 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every executable package to an exact reviewed version, for example `convex@x.y.z` and `mcporter@x.y.z`, rather than using `@latest` or no version. 2. Maintain and publish a lockfile with package integrity metadata where the installation model supports it. 3. Prefer trusted, preinstalled local binaries over runtime downloads. 4. Verify package provenance, signatures, checksums, and registry configuration before execution. 5. Perform dependency updates through a controlled review process rather than automatically resolving the newest release. 6. Run deployment tooling in a restricted environment with only the minimum required credential and filesystem access. 7. Avoid exposing unrelated secrets to MCP, bridge, and deployment processes. 8. Document the approved versions in both `SKILL.md` and `README.md` so operational instructions remain consistent. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The description claims a functional backend storage skill for multiple kinds of persisted data, but the supplied code is only a minimal Convex component definition/config file. There is no logic for reading, writing, persisting, or managing any data. This is a materially different actual behavior from the declared purpose, so it should be flagged as a mismatch.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The README instructs users to start an MCP server via `npx` with a floating package version, which executes whatever package version is current at runtime. In a secrets-handling skill, this creates a supply-chain risk: a compromised or malicious upstream release could run arbitrary code with access to local environment variables such as `CONVEX_DEPLOY_KEY` and the user's Convex context.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
`npx -y convex@latest mcp start` explicitly tracks the latest release, guaranteeing unreviewed code execution on each run. Because this skill is specifically for persistent secrets and backend memory, a malicious or compromised new version could exfiltrate credentials or alter stored data.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The fallback command `npx -y mcporter --version` pulls and executes an unpinned package from the registry at runtime. Although this invocation is only checking a version, it still runs package code and therefore creates a supply-chain execution path on a machine likely holding deployment credentials.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The command `npx -y mcporter list` executes an unpinned remote package version, exposing users to arbitrary upstream code changes. In the context of a backend-integration skill, the tool may run in an environment containing sensitive configuration and secrets, increasing the consequences of compromise.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
`npx -y mcporter list convex --schema` again relies on an unpinned package from the public registry, allowing silent behavior changes or malicious package substitution. The skill's purpose of managing memory and secrets makes even schema inspection commands risky if they execute attacker-controlled code locally.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The example `npx -y mcporter call convex.run --args ...` instructs direct execution of an unpinned package while interacting with the backend. If the fetched package is compromised, it could tamper with requests, read secrets, or exfiltrate deployment context during an operation that users expect to be trusted.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
The deployment instruction `CONVEX_DEPLOY_KEY=... npx -y convex@latest deploy` combines an unpinned executable with an inline secret, making this especially sensitive. A malicious latest package could immediately capture the deploy key and gain backend access, which is more dangerous here because the skill explicitly centralizes long-term memory and secrets in Convex.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The redeploy instruction again uses `npx -y convex@latest deploy`, reintroducing a floating-version supply-chain risk during a privileged operation. Because redeploys modify backend functions and may run in environments containing deployment credentials, exploitation could lead to code execution, secret theft, or persistent backend compromise.

Vague Triggers

Medium
Confidence
85% confidence
Finding
The markdown says "Use this skill when you want" followed by broad goals like durable memory and secret handling, but it does not define clear trigger phrases, scope boundaries, or exclusion conditions. In a skill-discovery or auto-invocation setting, these generic intents could overlap with many ordinary requests about memory, logs, or secrets.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The skill instructs users to launch the Convex MCP server via an unpinned package reference, which allows whatever version is current at execution time to be fetched and run. Because this skill handles secrets and persistent memory, a compromised or breaking upstream release could execute with access to deployment credentials and sensitive stored data.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
This guidance runs `mcporter` through an unpinned `npx` invocation, so the resolved package can change over time or be replaced through supply-chain compromise. Even though the command shown is only a version check, it still executes downloaded code in the user's environment.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The skill tells users to execute `mcporter list` from an unpinned package source, creating a supply-chain risk path. In this context, the tool is part of the bridge to secret and memory operations, so compromise could affect sensitive workflows.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
This schema-inspection command also executes an unpinned `mcporter` package, exposing users to arbitrary upstream changes or malicious releases. Because it is part of setup validation, users are likely to run it early and trust its output without scrutiny.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The documentation recommends `npx mcporter` generically if `mcporter` is not installed, again introducing unpinned remote code execution into the workflow. That is risky because this skill is specifically designed to manage secrets and persistent agent data.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The `mcporter call convex.run` example executes an unpinned bridge package in a workflow that may read or write secrets and long-term memory. A malicious or unexpected package version could intercept arguments, exfiltrate data, or alter behavior silently.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The re-run preflight command again relies on an unpinned `mcporter` package. Repeated use throughout the skill increases the chance that users will adopt insecure execution habits for a sensitive integration.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
The deploy instruction uses `npx -y convex@latest deploy`, explicitly tracking the latest release at deploy time. This is especially dangerous because deployment runs with `CONVEX_DEPLOY_KEY`, so any malicious or broken upstream release could gain privileged access to the user's backend.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The text normalizes `npx convex deploy` without version pinning for a privileged deployment action. Since deployment context includes sensitive credentials and modifies backend code, this creates a meaningful supply-chain and integrity risk.

Ssd 3

Medium
Confidence
92% confidence
Finding
The skill instructs the agent to persist broad categories of user facts, preferences, decisions, daily logs, and to automatically reload them at session startup. This creates a substantial data retention and disclosure risk because sensitive personal, operational, or credential-adjacent information may be stored indefinitely and resurfaced across sessions, especially in shared or multi-agent deployments.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
This long-term memory example again invokes unpinned `mcporter`, which is problematic because the command includes structured user data that may be sensitive. A compromised package could capture memory contents or manipulate writes to the backend.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The README instructs users to run `npx -y convex@latest deploy`, which fetches and executes the latest published CLI version at deploy time rather than a pinned, reviewed version. This creates a supply-chain risk: a malicious or compromised future release could execute arbitrary code in the user's environment and access sensitive deployment credentials such as `CONVEX_DEPLOY_KEY`.

Vague Triggers

Low
Confidence
74% confidence
Finding
The text says to start migration "when this skill is installed on an existing system with local/internal crons or non-Convex background functions," but it does not define how to determine that condition or what counts as in-scope background functionality. This leaves activation of the migration flow somewhat subjective and potentially over-broad.

Description-Behavior Mismatch

Low
Confidence
86% confidence
Finding
The manifest description explicitly includes storing secrets, long-term memory, daily logs, and custom data. In this file, the implemented operations cover agent memory entries and daily logs only; there is no secret-oriented storage path, access control distinction, or secret-management behavior present. This creates a partial description/behavior mismatch for this module.