clawvault 2.4.7 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/command-runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import path from 'path';
|
|
3
4
|
import {
|
|
4
5
|
ClawVault,
|
|
5
6
|
QmdUnavailableError,
|
|
@@ -7,6 +8,38 @@ import {
|
|
|
7
8
|
resolveVaultPath as resolveConfiguredVaultPath
|
|
8
9
|
} from '../dist/index.js';
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Validates that a path is within an allowed base directory.
|
|
13
|
+
* Prevents path traversal attacks.
|
|
14
|
+
* @param {string} inputPath - The path to validate
|
|
15
|
+
* @param {string} basePath - The allowed base directory
|
|
16
|
+
* @returns {string} The resolved, validated path
|
|
17
|
+
* @throws {Error} If the path escapes the base directory
|
|
18
|
+
*/
|
|
19
|
+
export function validatePathWithinBase(inputPath, basePath) {
|
|
20
|
+
const resolvedBase = path.resolve(basePath);
|
|
21
|
+
const resolvedPath = path.resolve(basePath, inputPath);
|
|
22
|
+
|
|
23
|
+
if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
|
|
24
|
+
throw new Error(`Path traversal detected: ${inputPath} escapes ${basePath}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return resolvedPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Sanitizes an argument that may contain a path to prevent injection.
|
|
32
|
+
* @param {string} arg - The argument to sanitize
|
|
33
|
+
* @returns {string} The sanitized argument
|
|
34
|
+
*/
|
|
35
|
+
function sanitizeQmdArg(arg) {
|
|
36
|
+
// Reject arguments with null bytes (injection attempt)
|
|
37
|
+
if (arg.includes('\0')) {
|
|
38
|
+
throw new Error('Invalid argument: contains null byte');
|
|
39
|
+
}
|
|
40
|
+
return arg;
|
|
41
|
+
}
|
|
42
|
+
|
|
10
43
|
export function resolveVaultPath(vaultPath) {
|
|
11
44
|
return resolveConfiguredVaultPath({ explicitPath: vaultPath });
|
|
12
45
|
}
|
|
@@ -19,7 +52,9 @@ export async function getVault(vaultPath) {
|
|
|
19
52
|
|
|
20
53
|
export async function runQmd(args) {
|
|
21
54
|
return new Promise((resolve, reject) => {
|
|
22
|
-
|
|
55
|
+
// Sanitize all arguments before passing to spawn
|
|
56
|
+
const sanitizedArgs = args.map(sanitizeQmdArg);
|
|
57
|
+
const proc = spawn('qmd', sanitizedArgs, { stdio: 'inherit' });
|
|
23
58
|
proc.on('close', (code) => {
|
|
24
59
|
if (code === 0) resolve();
|
|
25
60
|
else reject(new Error(`qmd exited with code ${code}`));
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Core vault lifecycle and write command registrations.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { validatePathWithinBase } from './command-runtime.js';
|
|
6
|
+
|
|
5
7
|
export function registerCoreCommands(
|
|
6
8
|
program,
|
|
7
9
|
{ chalk, path, fs, createVault, getVault, runQmd }
|
|
@@ -179,7 +181,10 @@ export function registerCoreCommands(
|
|
|
179
181
|
let content = options.content || '';
|
|
180
182
|
|
|
181
183
|
if (options.file) {
|
|
182
|
-
|
|
184
|
+
// Validate file path is within current working directory to prevent path traversal
|
|
185
|
+
const cwd = process.cwd();
|
|
186
|
+
const resolvedFilePath = validatePathWithinBase(options.file, cwd);
|
|
187
|
+
content = fs.readFileSync(resolvedFilePath, 'utf-8');
|
|
183
188
|
} else if (options.stdin) {
|
|
184
189
|
content = fs.readFileSync(0, 'utf-8');
|
|
185
190
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Vault operation command registrations (browse/sync/reindex/remember/shell-init/dashboard).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { validatePathWithinBase } from './command-runtime.js';
|
|
6
|
+
|
|
5
7
|
export function registerVaultOperationsCommands(
|
|
6
8
|
program,
|
|
7
9
|
{
|
|
@@ -232,7 +234,10 @@ export function registerVaultOperationsCommands(
|
|
|
232
234
|
const vault = await getVault(options.vault);
|
|
233
235
|
let content = options.content || '';
|
|
234
236
|
if (options.file) {
|
|
235
|
-
|
|
237
|
+
// Validate file path is within current working directory to prevent path traversal
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
const resolvedFilePath = validatePathWithinBase(options.file, cwd);
|
|
240
|
+
content = fs.readFileSync(resolvedFilePath, 'utf-8');
|
|
236
241
|
} else if (options.stdin) {
|
|
237
242
|
content = fs.readFileSync(0, 'utf-8');
|
|
238
243
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawvault",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "Structured memory system for AI agents — typed storage, knowledge graph, context profiles, canvas dashboards, neural graph themes, and Obsidian-native task views. An elephant never forgets. 🐘",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|