Back to skill

Security audit

Nutrition Claw

Security checks for vulnerabilities and agentic risk

Overview

The skill is a local nutrition tracker, but it needs Review because it stores sensitive health data and has a verified bug that can read or overwrite JSON files outside its intended log folder.

Review this before installing on a shared machine or using it with untrusted agent-supplied dates. It stores personal nutrition and body-goal data locally in plain files under ~/.nutrition-claw, may create extra feedback/education history, installs npm dependencies without a reviewed lockfile, and should validate dates before it is trusted for automated use.

Vulnerability Patterns
  • 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
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
src/lib/storage.ts:44
Finding
Path Traversal Through Unvalidated Date Values Permits JSON File Access Outside the Log Directory<![CDATA[ ## Vulnerability Details **File Location**: `src/lib/storage.ts:44-53`; input reaches these functions without format validation through `src/commands/meal.ts:68-77` and `src/commands/meal.ts:93-104` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code ```ts function logPath(date: string): string { return join(LOGS_DIR, `${date}.json`); } export function readDayLog(date: string): DayLog { return readJson<DayLog>(logPath(date), []); } export function writeDayLog(date: string, log: DayLog): void { writeJson(logPath(date), log); } ``` The caller only verifies that the date and time values are present: ```ts function requireDate(args: Args): string { const d = args['date'] as string | undefined; if (!d) { err('--date YYYY-MM-DD is required'); process.exit(1); } return d; } function requireTime(args: Args): string { const t = args['time'] as string | undefined; if (!t) { err('--time HH:MM is required'); process.exit(1); } return t; } ``` A meal operation subsequently uses the unvalidated date: ```ts async function mealAdd(args: Args): Promise<void> { const date = requireDate(args); const time = requireTime(args); const name = args['name'] as string | undefined; if (!name) { err('--name is required'); process.exit(1); } const id = nanoid(8); const meal: Meal = { id, time, name, ingredients: [] }; const log = readDayLog(date); log.push(meal); writeDayLog(date, log); await upsertVector(mealVecId(id), name, { type: 'meal', mealId: id, date }); print({ id }); } ``` ### Technical Analysis The command documentation specifies dates in `YYYY-MM-DD` format, but `requireDate()` only checks whether a value is present. It does not validate the date syntax, reject path separators, or verify that the resolved path remains under `LOGS_DIR`. `logPath()` concatenates the attacker-controlled value with `.json` and passes it to `path.join()`. A value containing `../` ...[truncated 2200 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the documented format before any filesystem operation: ```ts function requireDate(args: Args): string { const value = args.date; if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) { throw new Error('--date must use YYYY-MM-DD'); } const parsed = new Date(`${value}T00:00:00Z`); if ( Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value ) { throw new Error('invalid calendar date'); } return value; } ``` 2. Apply equivalent strict validation to time values, such as `^(?:[01]\d|2[0-3]):[0-5]\d$`. 3. Add defense-in-depth containment in the storage layer: ```ts import { dirname, resolve, sep } from 'path'; function logPath(date: string): string { const logsRoot = resolve(LOGS_DIR); const candidate = resolve(logsRoot, `${date}.json`); if (dirname(candidate) !== logsRoot) { throw new Error('invalid log path'); } return candidate; } ``` 4. Never rely solely on documentation to constrain filesystem-bound input. 5. Add automated tests for `../`, absolute paths, encoded separators, malformed dates, impossible calendar dates, and valid boundary dates. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/lib/storage.ts:17
Finding
Sensitive Health Records Are Created Without Explicit Restrictive Filesystem Permissions<![CDATA[ ## Vulnerability Details **File Location**: `src/lib/storage.ts:17-19`, `src/lib/storage.ts:32-34`, and `src/lib/storage.ts:99-109` **Vulnerability Type**: Insufficient protection of locally stored sensitive data **Risk Level**: Medium ### Vulnerable Code Directories are created without an explicit mode: ```ts export function ensureDirs(): void { for (const dir of [BASE_DIR, LOGS_DIR, VECTORS_DIR]) { if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); } } ``` JSON records are also written without an explicit mode: ```ts function writeJson(path: string, data: unknown): void { writeFileSync(path, JSON.stringify(data, null, 2)); } ``` The education history is handled in the same manner: ```ts export function appendEducationLog(foodName: string): void { const lines = existsSync(EDUCATION_FILE) ? readFileSync(EDUCATION_FILE, 'utf8').split('\n').map(l => l.trim()).filter(Boolean) : []; // Remove existing entry for this food if present (re-insertion at bottom = most recent) const filtered = lines.filter(l => l !== foodName); filtered.push(foodName); // Keep only the last EDUCATION_MAX_LINES const trimmed = filtered.slice(-EDUCATION_MAX_LINES); writeFileSync(EDUCATION_FILE, trimmed.join('\n') + '\n'); } ``` ### Technical Analysis The application stores potentially sensitive personal information, including nutrition goals, body-derived targets, meal history, food habits, and semantic indexes. However, it relies entirely on the process umask when creating directories and files. On systems with a permissive umask, newly created files may be readable by group members or other local users. Existing files with overly broad modes also remain broad because overwriting a file does not automatically tighten its permissions. This is a local confidentiality weakness rather than remote code execution. Exploitability depends on the host's umask, filesystem permission model, and whether other users or processes can access t ...[truncated 1167 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create private application directories explicitly: ```ts mkdirSync(dir, { recursive: true, mode: 0o700, }); ``` 2. Create sensitive files with owner-only permissions: ```ts writeFileSync(path, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600, }); ``` 3. Apply mode `0o600` to the education file and any vector-index files where supported. 4. During initialization, inspect and tighten existing paths rather than protecting only newly created files: ```ts chmodSync(BASE_DIR, 0o700); chmodSync(LOGS_DIR, 0o700); chmodSync(VECTORS_DIR, 0o700); ``` 5. Use atomic writes through an owner-only temporary file followed by `renameSync()` to reduce integrity risks from interrupted writes. 6. Document that these records contain sensitive health-related information and should not be placed on broadly shared volumes without appropriate access controls. ]]>

T08 · Insecure Dependencies

Note
Location
package.json:44
Finding
Unlocked Ranged Dependencies Make Installations Non-Reproducible and Increase Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `package.json:44-58`; installation guidance at `SKILL.md:9-14` **Vulnerability Type**: Unlocked third-party dependency resolution **Risk Level**: Low ### Vulnerable Code The package specifies ranged dependency versions: ```json "dependencies": { "@inquirer/prompts": "^7.5.2", "@themaximalist/embeddings.js": "^0.1.3", "@xenova/transformers": "^2.17.2", "debug": "^4.4.3", "nanoid": "^5.1.5", "vectra": "^0.12.3", "yaml": "^2.7.1" }, "devDependencies": { "@types/bun": "latest", "typescript": "^5.8.2" } ``` The installation instructions use dependency resolution without a lockfile: ```bash cd <skill-folder> npm install ``` No package lockfile is present in the supplied project structure. ### Technical Analysis Caret ranges allow future compatible releases to be selected at installation time, while `"latest"` is explicitly mutable. Without a committed lockfile, separate installations can resolve different transitive dependency graphs from the code that was reviewed. This does not establish that any listed dependency is currently malicious or vulnerable. The weakness is that the reviewed source does not fully determine the code that will later be installed and executed. npm dependencies may include lifecycle scripts, native components, model-loading code, and substantial transitive dependency trees. The project also uses local embedding and transformer packages that may download model assets as part of runtime behavior. That model download is disclosed by the documentation, but its integrity should still be controlled. ### Attack Path 1. A direct or transitive dependency publishes a compromised release that satisfies an existing version range, or the mutable `"latest"` development dependency changes. 2. A user follows the documented `npm install` procedure. 3. npm resolves the newer package because no reviewed lockfile fixes the exact artifact graph. 4. Any dependency lifecycle script ...[truncated 902 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate, review, and commit an npm-compatible lockfile. 2. Use reproducible installation in documentation and automation: ```bash npm ci ``` 3. Replace `"latest"` with an exact reviewed version and consider exact pinning for security-sensitive runtime packages. 4. Review direct and transitive dependency lifecycle scripts before release. 5. Run dependency vulnerability and provenance checks in continuous integration. 6. Record and verify integrity metadata for downloaded model assets where the underlying libraries support it. 7. Use automated dependency updates that produce isolated, reviewable changes rather than resolving new versions implicitly during end-user installation. ]]>
Vulnerability Patterns
  • 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
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (26)

Credential Access

High
Category
Privilege Escalation
Content
Or configure npm once to always resolve the `@pita` scope from GitHub Packages:

```bash
echo "@pita:registry=https://npm.pkg.github.com" >> ~/.npmrc
npm install -g @pita/nutrition-claw
```
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Skill Enumeration

Medium
Category
Agent Snooping
Content
./scripts/sync-skill.sh
```

This copies `SKILL.md` to `~/.openclaw/workspace/skills/nutrition/SKILL.md` so the openclaw agent always sees the latest version. **Do not skip this step — an outdated skill file will cause the agent to give wrong guidance.**
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Session Persistence

Medium
Category
Rogue Agent
Content
`--date` and `--time` are always required for `meal add`. Meal nutrition totals are computed from their ingredients — never stored separately.

```bash
# Create a meal — returns the meal id
nutrition-claw meal add --name "Lunch" --date 2026-03-15 --time 13:00
# id: D-lfLPOP
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly instructs the agent to create and maintain a persistent file at ~/.nutrition-claw/feedback-diary.txt, but the user-facing description does not clearly disclose this extra write beyond core meal/food storage. Hidden or under-disclosed persistence is dangerous because it causes the agent to retain behavioral metadata across sessions, which can surprise users and expand the local privacy footprint.

Ssd 3

Medium
Confidence
96% confidence
Finding
The diary stores a persistent line-by-line history of dates, normalized food names, and topical interaction metadata that is not necessary for basic nutrition logging. This creates an additional behavioral profile of the user's eating inputs and agent responses, increasing privacy risk if the local machine is shared, compromised, or backed up to third-party services.

Ssd 3

Medium
Confidence
97% confidence
Finding
The workflow requires the agent to read, rewrite, purge, and append to a persistent diary on every ingredient add or update, normalizing user inputs and continuously mutating local state. Repeated autonomous file operations increase the chance of unintended data retention, corruption, or privacy leakage, especially because they occur as a side effect of ordinary nutrition actions rather than a clearly separated consented feature.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
In flag-based mode, the command persists data via writeGoals(goals) immediately and only prints the resulting object afterward. While interactive mode includes an explicit confirmation before saving, the non-interactive path lacks a comparable warning, prompt, or comment disclosing that user-provided health-related settings will be written to storage.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code adds, updates, and deletes meal data and ingredient records via writeDayLog, and also deletes/rebuilds vector entries, which are persistent state changes. While the command names imply CRUD behavior, there is no inline user-facing warning, confirmation prompt, or explanatory comment/docstring disclosing that these operations modify or remove stored data.

Session Persistence

Medium
Category
Rogue Agent
Content
goals set [nutrients...]          Update individual goal values
  goals delete [--nutrient <key>]   Delete one or all goals

  meal add --name <n> --date <d> --time <t>   Create meal, returns id
  meal list --date <d>                         List meals with totals
  meal update <id> --name <n>                  Rename meal
  meal delete <id>                             Delete meal + ingredients
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code synchronously writes goals, food library, daily logs, and education history to files under the user's home directory, which affects user data persistence. While there are comments describing some functions, there is no confirmation prompt, user-facing log/print, or explicit disclosure here that personal nutrition-related data is being stored on disk.

Missing User Warnings

Low
Confidence
89% confidence
Finding
This markdown file documents commands that append to `~/.npmrc` and later explains that the tool stores nutrition goals, foods, and logs under `~/.nutrition-claw/`. While these behaviors are described, the README does not present them as a user-facing warning about persistent changes to local configuration and storage of sensitive personal data.

Natural-Language Policy Violations

Low
Confidence
72% confidence
Finding
These instructions require the assistant to always provide a specific style of response rather than offering the user a choice in how guidance is delivered. While not a spoken-language locale restriction, it is a natural-language policy concern because it forces a fixed interaction mode without opt-in or preference selection.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"clawhub:publish": "npm run build && ./scripts/sync-skill.sh && clawhub publish ~/.openclaw/workspace/skills/nutrition-claw --version $npm_package_version"
  },
  "dependencies": {
    "@inquirer/prompts": "^7.5.2",
    "@themaximalist/embeddings.js": "^0.1.3",
    "@xenova/transformers": "^2.17.2",
    "debug": "^4.4.3",
Confidence
90% confidence
Finding
Using a caret range for @inquirer/prompts allows newer minor/patch releases to be installed without explicit review, which weakens supply-chain integrity and reproducibility. In an agent skill package, that can change runtime behavior across environments and could expose consumers to a compromised upstream release.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "@inquirer/prompts": "^7.5.2",
    "@themaximalist/embeddings.js": "^0.1.3",
    "@xenova/transformers": "^2.17.2",
    "debug": "^4.4.3",
    "nanoid": "^5.1.5",
Confidence
90% confidence
Finding
The dependency @themaximalist/embeddings.js is specified with a caret range, so installations may pull different package contents over time. That creates supply-chain risk and makes it harder to verify exactly what code executes in this local-first CLI skill.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "@inquirer/prompts": "^7.5.2",
    "@themaximalist/embeddings.js": "^0.1.3",
    "@xenova/transformers": "^2.17.2",
    "debug": "^4.4.3",
    "nanoid": "^5.1.5",
    "vectra": "^0.12.3",
Confidence
90% confidence
Finding
The @xenova/transformers dependency is not pinned to a single exact version, allowing unreviewed upstream updates into builds. Because this package is involved in model-related functionality, unexpected updates could materially alter behavior or introduce vulnerable transitive code.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@inquirer/prompts": "^7.5.2",
    "@themaximalist/embeddings.js": "^0.1.3",
    "@xenova/transformers": "^2.17.2",
    "debug": "^4.4.3",
    "nanoid": "^5.1.5",
    "vectra": "^0.12.3",
    "yaml": "^2.7.1"
Confidence
95% confidence
Finding
The debug package is declared with a caret range, so the actual installed version may vary and could include a compromised or vulnerable release. This is more concerning here because there are known advisories affecting some debug versions, but the manifest does not constrain resolution tightly enough to verify safety.

Unverifiable Dependency: debug has 4 known advisory(ies) (CVE-2025-59144 (debug@4.4.2 contains malware after npm account takeover); CVE-2017-20165 (debug Inefficient Regular Expression Complexity vulnerability); CVE-2017-16137 (Regular Expression Denial of Service in debug) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
87% confidence
Finding
The manifest does not pin debug to an exact version, and known advisories exist for some releases, including a reported malware-compromise event and ReDoS issues. Because the resolved version cannot be verified from this file alone, consumers may install an affected version depending on lockfile state and timing.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@themaximalist/embeddings.js": "^0.1.3",
    "@xenova/transformers": "^2.17.2",
    "debug": "^4.4.3",
    "nanoid": "^5.1.5",
    "vectra": "^0.12.3",
    "yaml": "^2.7.1"
  },
Confidence
95% confidence
Finding
The nanoid dependency is specified with a caret range, which permits silent version drift and reduces assurance about which implementation is executed. Since nanoid often underpins identifier generation, vulnerable or behavior-changing releases can affect reliability and, in some cases, security properties.

Unverifiable Dependency: nanoid has 5 known advisory(ies) (CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2024-55565 (Predictable results in nanoid generation when given non-integer values) +2 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
84% confidence
Finding
The nanoid dependency is version-ranged and known advisories exist for some releases, so the package manifest alone cannot prove that installs are safe. If an affected version is resolved, identifier generation could become unreliable or predictable in edge cases, depending on how the library is used.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@xenova/transformers": "^2.17.2",
    "debug": "^4.4.3",
    "nanoid": "^5.1.5",
    "vectra": "^0.12.3",
    "yaml": "^2.7.1"
  },
  "devDependencies": {
Confidence
90% confidence
Finding
The vectra package is unpinned and may resolve to different versions over time, creating a supply-chain and reproducibility weakness. In a package used by agents, nondeterministic dependency resolution can make security review and incident response more difficult.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"debug": "^4.4.3",
    "nanoid": "^5.1.5",
    "vectra": "^0.12.3",
    "yaml": "^2.7.1"
  },
  "devDependencies": {
    "@types/bun": "latest",
Confidence
95% confidence
Finding
The yaml dependency is not pinned exactly, so installations may pull in versions with different security properties. This matters because YAML parsers are historically high-risk components, and known advisories exist for some yaml releases, making unverifiable version drift a real concern.

Unverifiable Dependency: yaml has 2 known advisory(ies) (CVE-2026-33532 (yaml is vulnerable to Stack Overflow via deeply nested YAML collections); CVE-2023-2251 (Uncaught Exception in yaml)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
Known vulnerabilities affect some yaml releases, but the dependency is not pinned exactly, so the actual security posture cannot be determined from the manifest. Since YAML parsing libraries can be exposed to denial-of-service or parser-crash issues, unresolved version ambiguity is a meaningful supply-chain risk.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"yaml": "^2.7.1"
  },
  "devDependencies": {
    "@types/bun": "latest",
    "typescript": "^5.8.2"
  }
}
Confidence
98% confidence
Finding
The devDependency uses the floating tag "latest", which makes builds non-reproducible and can silently introduce malicious or breaking upstream releases. Even though this is a development-only package, compromised developer environments or CI pipelines can still be affected during install or build.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/bun": "latest",
    "typescript": "^5.8.2"
  }
}
Confidence
88% confidence
Finding
The TypeScript devDependency uses a caret range, which allows uncontrolled minor/patch updates in developer and CI environments. While lower risk than a runtime dependency, this can still affect build integrity, reproducibility, and exposure to toolchain compromise.

Intent-Code Divergence

Low
Confidence
90% confidence
Finding
The inline documentation states a clear intent limitation: "Food library is for packaged/labelled products only." However, the implementation only requires a name and optional nutrient fields, with no validation or checks that the item is packaged or label-derived. This is an intent-code divergence because the comment asserts a narrower scope than the code actually enforces.

Static analysis

No suspicious patterns detected.