Back to skill

Security audit

Plan2meal

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Plan2Meal recipe and grocery skill, but it handles OAuth tokens and backend routing in ways users should review carefully before installing.

Install only if you trust the configured Plan2Meal backend and OAuth app operators. Prefer a self-hosted HTTPS CONVEX_URL you control, avoid enabling optional providers you do not need, and treat login as granting the skill temporary access to your provider profile token and Plan2Meal recipe/grocery data. The publisher should validate backend origins, exchange provider tokens for scoped Plan2Meal tokens, bind OAuth state to sessions using cryptographic randomness, restrict recipe URL schemes/targets, add delete confirmation, and pin dependencies.

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
src/convex.ts:18
Finding

Provider OAuth Tokens Are Forwarded to an Unrestricted Backend Origin

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/index.ts:330
Finding

OAuth State Is Not Cryptographically Generated or Bound to the Initiating Session

Content
View full analysis
OAUTH_STATE_TTL_MS) { return { text: '❌ OAuth session expired. Please run `plan2meal login` again.' }; } try { const accessToken = await exchangeCodeForAccessToken(stateEntry.provider, code); const userInfo = await fetchUserInfo(stateEntry.provider, accessToken); sessionStore.set(sessionId, { authToken: accessToken, provider: stateEntry.provider, ``` ### Technical Analysis OAuth state is generated with `Math.random()`, which is not a cryptographically secure random-number generator. More importantly, each state record stores only the provider and creation time. It does not record the session that initiated authorization. During callback processing, the implementation verifies only that the state exists globally and has not expired. The resulting ...[truncated 1890 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/utils.ts:24
Finding

Recipe URL Validation Permits Unsafe Schemes and Internal Network Targets

Content
View full analysis
{ return this.action('recipeExtraction.fetchRecipeMetadata', { url }); } ``` ### Technical Analysis `isValidUrl` checks only whether the JavaScript `URL` constructor can parse the input. It does not restrict the scheme to HTTP or HTTPS and does not reject localhost, private networks, link-local addresses, reserved addresses, embedded credentials, or unusual ports. The accepted value is forwarded to the remote backend's `recipeExtraction.fetchRecipeMetadata` action. The action name and returned scraping metadata indicate that the backend is expected to fetch the supplied URL. However, the backend implementation is not included in this repository, so successful access to internal resources cannot be established from the audited code alone. The confirmed client-side flaw is failure to enforce the declared recipe-URL boundary. SSRF impact is contingent on the backend fetcher's own validation and network access. ### Attack Path 1. A user submits `plan2meal add` with a syntactically valid but unsafe URL, such as an internal HTTP address, loopback target, link-local metadata address, or non-HTTP scheme. 2. `isValidUrl` accepts the ...[truncated 1007 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

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 · README.md (reported line 18)May include surrounding context.

clawdhub install plan2meal

Configure environment

cp .env.example .env

Set CONVEX_URL and OAuth credentials

text

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared purpose focuses on recipe and grocery management, but the skill also performs OAuth flows, callback handling, token exchange, profile retrieval, and session/state management. This mismatch can mislead users and reviewers about the sensitivity of the skill, causing them to authorize identity-provider access and backend trust they did not expect.

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 53)May include surrounding context.

  1. Configure environment:

    bash
    cp .env.example .env
    
  2. Environment variables:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill declares access to environment-backed capabilities but does not define an explicit tool scope such as permissions or allowed-tools. That weakens least-privilege controls and makes it harder for operators and users to understand what the skill is allowed to access, increasing the chance of unintended secret or backend use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The deleteRecipe handler performs an irreversible delete via this.convex.deleteRecipe(id) and only reports success after the deletion completes. There is no confirmation prompt, pre-action warning, or comment/docstring disclosing the destructive behavior beyond the command name itself.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The client exposes a getAllRecipes() method that calls recipes.get, which appears broader than the skill’s declared user-scoped behavior and sits alongside a safer getMyRecipes() method. In a chat-driven skill, this creates unnecessary capability for bulk data access if the backend function is not strictly user-authorized, increasing the risk of over-collection or cross-tenant recipe disclosure.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code reads multiple third-party OAuth client secrets from environment variables and manages external login state locally, even though the skill is presented as a recipe/grocery integration. Handling unrelated OAuth secrets and sessions increases the blast radius of compromise and creates a mismatch between expected functionality and actual privileged behavior.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill adds generic multi-provider OAuth flows that are broader than the declared Plan2Meal recipe/grocery scope. This expands the trust boundary to GitHub, Google, and Apple identity systems and creates unnecessary collection and handling of third-party auth artifacts, increasing attack surface and privacy risk if the skill or backend is misconfigured or abused.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill exchanges authorization codes and client credentials with external OAuth providers, but users are not clearly informed at the execution point that their data and auth artifacts will be transmitted to GitHub, Google, or Apple. In a chat skill context, hidden or insufficiently explicit external transmission can mislead users about where sensitive authentication data is going.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · src/index.ts (reported line 303)May include surrounding context.

ts
async function fetchUserInfo(provider: AuthProvider, token: string): Promise<UserInfo | null> {
  try {
    if (provider === 'github') {
      const r = await axios.get('https://api.github.com/user', {
        headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }
      });
      return {

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

OAuth access tokens are retained in server memory for up to seven days without explicit disclosure of token retention or session lifetime. Even though storage is in-memory, these tokens can be used to act as the user for the provider-backed session, so undisclosed retention increases privacy and session-hijacking risk.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The runtime dependency axios is specified with a caret range, which permits automatic installation of newer minor/patch releases rather than a single reviewed version. In a skill that likely performs remote API calls, this weakens supply-chain control and can accidentally pull in a vulnerable or behavior-changing release during fresh installs or CI builds.

Content

Scanner excerpt · package.json (reported line 17)May include surrounding context.

json
"author": "",
  "license": "MIT",
  "dependencies": {
    "axios": "^1.6.2"
  },
  "devDependencies": {
    "@eslint/js": "^9.17.0",

Unverifiable Dependency: axios has 16 known advisory(ies) (CVE-2026-44494 (axios Vulnerable to Full Man-in-the-Middle via Prototype Pollution Gadget in `co); CVE-2026-44495 (axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollut); CVE-2025-62718 (Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
80% confidence
Finding

Axios has known advisories, and because the manifest uses a broad caret range instead of an exact reviewed version, it is not possible to verify from this file whether installations will resolve to a safe release. In this Plan2Meal skill context, axios is likely used for outbound HTTP requests to APIs or recipe URLs, so unresolved client-side issues could increase exposure to SSRF, request routing bypass, credential leakage, or response tampering depending on how the library is used elsewhere.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
87% confidence
Finding

The dev dependency @eslint/js is not pinned to an exact version, so development and CI environments may resolve different releases over time. This is primarily a supply-chain integrity and build reproducibility issue rather than an immediate runtime exploit path.

Content

Scanner excerpt · package.json (reported line 20)May include surrounding context.

json
"axios": "^1.6.2"
  },
  "devDependencies": {
    "@eslint/js": "^9.17.0",
    "@types/node": "^20.10.0",
    "eslint": "^9.17.0",
    "typescript": "^5.3.0",

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
87% confidence
Finding

The dev dependency @types/node uses a caret range, allowing silent version drift across environments. While this package is not typically shipped to production, unpinned development dependencies still increase supply-chain risk and can affect build consistency.

Content

Scanner excerpt · package.json (reported line 21)May include surrounding context.

json
},
  "devDependencies": {
    "@eslint/js": "^9.17.0",
    "@types/node": "^20.10.0",
    "eslint": "^9.17.0",
    "typescript": "^5.3.0",
    "typescript-eslint": "^8.18.0"

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
87% confidence
Finding

The eslint package is specified as a version range instead of an exact version, which allows CI or local environments to pull different releases over time. This creates low-severity supply-chain and reproducibility risk, especially if development tooling runs with repository access or in privileged CI contexts.

Content

Scanner excerpt · package.json (reported line 22)May include surrounding context.

json
"devDependencies": {
    "@eslint/js": "^9.17.0",
    "@types/node": "^20.10.0",
    "eslint": "^9.17.0",
    "typescript": "^5.3.0",
    "typescript-eslint": "^8.18.0"
  }

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
88% confidence
Finding

The typescript compiler is not pinned exactly, so builds may change as newer compatible versions are released. This is mainly a low-risk supply-chain and reliability concern, but compiler changes can alter emitted code or break builds unexpectedly.

Content

Scanner excerpt · package.json (reported line 23)May include surrounding context.

json
"@eslint/js": "^9.17.0",
    "@types/node": "^20.10.0",
    "eslint": "^9.17.0",
    "typescript": "^5.3.0",
    "typescript-eslint": "^8.18.0"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
87% confidence
Finding

The typescript-eslint dependency is unpinned, allowing environment-dependent resolution of newer releases. As a development tool, the main risk is reduced build reproducibility and potential supply-chain exposure in development or CI rather than direct production compromise.

Content

Scanner excerpt · package.json (reported line 24)May include surrounding context.

json
"@types/node": "^20.10.0",
    "eslint": "^9.17.0",
    "typescript": "^5.3.0",
    "typescript-eslint": "^8.18.0"
  }
}

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/index.ts:20

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
src/index.ts:253