Back to skill

Security audit

Notebook

Security checks for vulnerabilities and agentic risk

Overview

Notebook is a coherent local note tool, but its type names can escape the intended data folder and create, modify, or delete files outside the notebook area.

Install only if you are comfortable with a review-needed local file tool. Keep notebook data in a low-privilege workspace, avoid untrusted type names or imported notebook files, and update the dependency versions before relying on it for important notes.

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

T09 · Insecure Skill Coding Practices

Error
Location
lib/store.js:159
Finding
Path Traversal Through Unvalidated Object Type Names<![CDATA[ ## Vulnerability Details **File Location**: `cli.js:79-113`, `lib/store.js:159-163`, and `lib/store.js:166-197` **Vulnerability Type**: Path traversal and unrestricted filesystem write **Risk Level**: High ### Vulnerable Code In `cli.js:79-113`, a type name supplied through the command line is stored without validation and can subsequently be used to create an object: ```js case 'type-add': { // Simple: type-add typename field1:text field2:select(a|b|c) const typeName = args[1]; if (!typeName) { console.log('\nUsage: notebook type-add typename field:text ...'); process.exit(1); } const fields = []; const fieldArgs = args.slice(2); for (const arg of fieldArgs) { const [name, typeWithOpts] = arg.split(':'); if (!typeWithOpts) continue; let type = typeWithOpts; let options = []; if (typeWithOpts.includes('(')) { const match = typeWithOpts.match(/(\w+)\(([^)]+)\)/); if (match) { type = match[1]; options = match[2].split('|'); } } fields.push({ name, type: type === 'select' ? 'select' : type, options: options.length ? options : undefined, required: true }); } saveType({ name: typeName, fields }); console.log(`\n✅ Type "${typeName}" created with ${fields.length} fields.`); break; } ``` In `lib/store.js:159-163`, that type name is used directly as a filesystem path component: ```js function getObjectPath(type, id) { const typeDir = path.join(OBJECTS_DIR, type); if (!fs.existsSync(typeDir)) fs.mkdirSync(typeDir, { recursive: true }); return path.join(typeDir, `${id}.yaml`); } ``` The resulting path is then used for file creation in `lib/store.js:166-197`: ```js function createObject(typeName, data) { const type = getType(typeName); if (!type) throw new Error(`Type "${typeName}" not found`); const id = generateId(); const object = { id, type: typeName, created: today(), updated: new Dat ...[truncated 3500 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Strictly validate type names at every trust boundary.** Permit only a conservative identifier format: ```js const TYPE_NAME_PATTERN = /^[A-Za-z0-9_-]+$/; function validateTypeName(typeName) { if ( typeof typeName !== 'string' || !TYPE_NAME_PATTERN.test(typeName) ) { throw new Error( 'Invalid type name: use only letters, numbers, underscores, and hyphens' ); } } ``` Apply this validation before saving, loading, deleting, or using a type name in any filesystem operation. 2. **Enforce canonical path containment.** Do not rely on input validation alone: ```js function getObjectPath(type, id) { validateTypeName(type); if (!/^[A-Za-z0-9_-]+$/.test(id)) { throw new Error('Invalid object ID'); } const objectsRoot = path.resolve(OBJECTS_DIR); const typeDir = path.resolve(objectsRoot, type); const filePath = path.resolve(typeDir, `${id}.yaml`); if ( typeDir !== objectsRoot && !typeDir.startsWith(objectsRoot + path.sep) ) { throw new Error('Object path escapes the storage directory'); } if (!filePath.startsWith(typeDir + path.sep)) { throw new Error('Object file path escapes the type directory'); } fs.mkdirSync(typeDir, { recursive: true }); return filePath; } ``` 3. **Validate persisted data after loading it.** Existing `types.yaml` and `index.json` files must be treated as untrusted because they can contain legacy or externally modified traversal values. 4. **Protect immutable metadata.** Reject updates to fields such as `id`, `type`, and `created` so object identity cannot diverge from its storage location: ```js const IMMUTABLE_FIELDS = new Set(['id', 'type', 'created']); for (const key of Object.keys(updates)) { if (IMMUTABLE_FIELDS.has(key)) { throw new Error(`Field "${key}" cannot be modified`); } } ``` ...[truncated 355 chars]
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (3)

Missing User Warnings

Low
Confidence
82% confidence
Finding
The skill advertises a `notebook delete typename title` command without warning about destructive behavior or requiring confirmation expectations. In a local-first knowledge base, accidental deletion could permanently remove user notes, tasks, or linked records, especially if the agent executes user requests too eagerly.

Known Vulnerable Dependency: js-yaml==4.1.1 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
98% confidence
Finding
The package declares js-yaml 4.1.1, which is flagged with multiple advisories involving CPU exhaustion during YAML parsing. This skill is explicitly YAML-based, so vulnerable parsing is central to its functionality and could allow maliciously crafted notebook files to trigger denial of service when opened or processed.

Known Vulnerable Dependency: uuid==13.0.0 — 1 advisory(ies): CVE-2026-41907 (uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided)

Low
Category
Supply Chain
Confidence
83% confidence
Finding
The declared uuid 13.0.0 version is reported as affected by a bounds-check issue when buffer arguments are supplied to certain UUID generation functions. The impact is lower because exploitation depends on the application actually calling the affected APIs with attacker-influenced buffer parameters, which is not evident from package.json alone.

Static analysis

No suspicious patterns detected.