shiva-code 0.5.0 → 0.5.2
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/dist/{chunk-IDM6KY2R.js → chunk-H5OFO4VS.js} +115 -22
- package/dist/{chunk-5RCSFT5F.js → chunk-LBTCSQAX.js} +1 -1
- package/dist/index.js +682 -391
- package/dist/{manager-LXNF7QWT.js → manager-ZPQWG7E6.js} +1 -1
- package/dist/{package-manager-WF3UW2J4.js → package-manager-NHNQATBH.js} +2 -2
- package/package.json +1 -1
|
@@ -5,11 +5,86 @@ import {
|
|
|
5
5
|
// src/services/session/manager.ts
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import * as fsPromises from "fs/promises";
|
|
8
|
+
import * as path2 from "path";
|
|
9
|
+
import * as os2 from "os";
|
|
10
|
+
import * as readline from "readline";
|
|
11
|
+
|
|
12
|
+
// src/utils/sanitize.ts
|
|
8
13
|
import * as path from "path";
|
|
9
14
|
import * as os from "os";
|
|
10
|
-
|
|
15
|
+
function isPathSafe(inputPath, allowedBase) {
|
|
16
|
+
try {
|
|
17
|
+
const resolved = path.resolve(inputPath);
|
|
18
|
+
if (inputPath.includes("\0")) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (allowedBase) {
|
|
22
|
+
const resolvedBase = path.resolve(allowedBase);
|
|
23
|
+
const relative2 = path.relative(resolvedBase, resolved);
|
|
24
|
+
if (relative2.startsWith("..") || path.isAbsolute(relative2)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isPathWithinAllowedBoundaries(inputPath) {
|
|
34
|
+
const resolved = path.resolve(inputPath);
|
|
35
|
+
const homeDir = os.homedir();
|
|
36
|
+
if (resolved.startsWith(homeDir)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const allowedPrefixes = [
|
|
40
|
+
"/tmp",
|
|
41
|
+
"/var/tmp",
|
|
42
|
+
homeDir
|
|
43
|
+
];
|
|
44
|
+
return allowedPrefixes.some((prefix) => resolved.startsWith(prefix));
|
|
45
|
+
}
|
|
46
|
+
function isValidSessionId(sessionId) {
|
|
47
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
51
|
+
return uuidRegex.test(sessionId);
|
|
52
|
+
}
|
|
53
|
+
function isValidProjectPath(projectPath) {
|
|
54
|
+
if (!projectPath || typeof projectPath !== "string") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (!path.isAbsolute(projectPath)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (projectPath.includes("\0")) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const dangerousForPath = /[;&|`$(){}[\]<>\\!#*?"'\n\r\t]/;
|
|
64
|
+
if (dangerousForPath.test(projectPath)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
function maskSecret(secret) {
|
|
70
|
+
if (!secret || secret.length < 8) {
|
|
71
|
+
return "********";
|
|
72
|
+
}
|
|
73
|
+
const visibleStart = secret.substring(0, 2);
|
|
74
|
+
const visibleEnd = secret.substring(secret.length - 2);
|
|
75
|
+
const maskedLength = Math.min(secret.length - 4, 20);
|
|
76
|
+
return `${visibleStart}${"*".repeat(maskedLength)}${visibleEnd}`;
|
|
77
|
+
}
|
|
78
|
+
function sanitizeForLog(input) {
|
|
79
|
+
if (!input || typeof input !== "string") {
|
|
80
|
+
return "[invalid]";
|
|
81
|
+
}
|
|
82
|
+
return input.replace(/[\x00-\x1F\x7F]/g, "").substring(0, 500);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/services/session/manager.ts
|
|
11
86
|
function getClaudeProjectsPath() {
|
|
12
|
-
return
|
|
87
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
13
88
|
}
|
|
14
89
|
function encodeProjectPath(projectPath) {
|
|
15
90
|
return projectPath.replace(/\//g, "-").replace(/^-/, "");
|
|
@@ -19,7 +94,7 @@ function decodeProjectPath(encoded) {
|
|
|
19
94
|
return "/" + parts.join("/");
|
|
20
95
|
}
|
|
21
96
|
function getProjectName(projectPath) {
|
|
22
|
-
return
|
|
97
|
+
return path2.basename(projectPath);
|
|
23
98
|
}
|
|
24
99
|
async function getAllClaudeProjects(skipCache = false) {
|
|
25
100
|
if (!skipCache) {
|
|
@@ -63,11 +138,12 @@ function invalidateSessionsCache() {
|
|
|
63
138
|
cache.invalidateSessions();
|
|
64
139
|
}
|
|
65
140
|
function getSessionIndex(encodedPath) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
141
|
+
const projectsPath = getClaudeProjectsPath();
|
|
142
|
+
const indexPath = path2.join(projectsPath, encodedPath, "sessions-index.json");
|
|
143
|
+
if (!isPathSafe(indexPath, projectsPath)) {
|
|
144
|
+
console.error(`[Security] Invalid path detected: ${sanitizeForLog(encodedPath)}`);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
71
147
|
if (!fs.existsSync(indexPath)) {
|
|
72
148
|
return null;
|
|
73
149
|
}
|
|
@@ -79,11 +155,12 @@ function getSessionIndex(encodedPath) {
|
|
|
79
155
|
}
|
|
80
156
|
}
|
|
81
157
|
async function getSessionIndexAsync(encodedPath) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
158
|
+
const projectsPath = getClaudeProjectsPath();
|
|
159
|
+
const indexPath = path2.join(projectsPath, encodedPath, "sessions-index.json");
|
|
160
|
+
if (!isPathSafe(indexPath, projectsPath)) {
|
|
161
|
+
console.error(`[Security] Invalid path detected: ${sanitizeForLog(encodedPath)}`);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
87
164
|
try {
|
|
88
165
|
const content = await fsPromises.readFile(indexPath, "utf-8");
|
|
89
166
|
return JSON.parse(content);
|
|
@@ -93,7 +170,7 @@ async function getSessionIndexAsync(encodedPath) {
|
|
|
93
170
|
}
|
|
94
171
|
async function findProject(nameOrPath, useCache = true) {
|
|
95
172
|
const projects = await getAllClaudeProjects(!useCache);
|
|
96
|
-
const absolutePath =
|
|
173
|
+
const absolutePath = path2.resolve(nameOrPath);
|
|
97
174
|
const byPath = projects.find((p) => p.absolutePath === absolutePath);
|
|
98
175
|
if (byPath) return byPath;
|
|
99
176
|
const lowerName = nameOrPath.toLowerCase();
|
|
@@ -103,7 +180,7 @@ async function findProject(nameOrPath, useCache = true) {
|
|
|
103
180
|
return byName || null;
|
|
104
181
|
}
|
|
105
182
|
function findProjectFromArray(projects, nameOrPath) {
|
|
106
|
-
const absolutePath =
|
|
183
|
+
const absolutePath = path2.resolve(nameOrPath);
|
|
107
184
|
const byPath = projects.find((p) => p.absolutePath === absolutePath);
|
|
108
185
|
if (byPath) return byPath;
|
|
109
186
|
const lowerName = nameOrPath.toLowerCase();
|
|
@@ -173,10 +250,15 @@ function isSessionActive(session) {
|
|
|
173
250
|
}
|
|
174
251
|
async function parseSessionFile(jsonlPath, level = "standard") {
|
|
175
252
|
const summaries = [];
|
|
253
|
+
const resolvedPath = path2.resolve(jsonlPath);
|
|
254
|
+
if (!isPathWithinAllowedBoundaries(resolvedPath)) {
|
|
255
|
+
console.error(`[Security] Path outside allowed boundaries: ${sanitizeForLog(jsonlPath)}`);
|
|
256
|
+
return summaries;
|
|
257
|
+
}
|
|
176
258
|
if (!fs.existsSync(jsonlPath)) {
|
|
177
259
|
return summaries;
|
|
178
260
|
}
|
|
179
|
-
return new Promise((
|
|
261
|
+
return new Promise((resolve3) => {
|
|
180
262
|
const fileStream = fs.createReadStream(jsonlPath, { encoding: "utf-8" });
|
|
181
263
|
const rl = readline.createInterface({
|
|
182
264
|
input: fileStream,
|
|
@@ -213,10 +295,10 @@ async function parseSessionFile(jsonlPath, level = "standard") {
|
|
|
213
295
|
}
|
|
214
296
|
});
|
|
215
297
|
rl.on("close", () => {
|
|
216
|
-
|
|
298
|
+
resolve3(summaries);
|
|
217
299
|
});
|
|
218
300
|
rl.on("error", () => {
|
|
219
|
-
|
|
301
|
+
resolve3(summaries);
|
|
220
302
|
});
|
|
221
303
|
});
|
|
222
304
|
}
|
|
@@ -261,14 +343,21 @@ function formatRecoveredContextAsMarkdown(context) {
|
|
|
261
343
|
return lines.join("\n");
|
|
262
344
|
}
|
|
263
345
|
function saveRecoveredContext(context, outputDir) {
|
|
264
|
-
const dir = outputDir ||
|
|
265
|
-
|
|
266
|
-
|
|
346
|
+
const dir = outputDir || path2.join(os2.homedir(), ".shiva", "recovery");
|
|
347
|
+
const resolvedDir = path2.resolve(dir);
|
|
348
|
+
if (!isPathWithinAllowedBoundaries(resolvedDir)) {
|
|
349
|
+
throw new Error(`Output directory outside allowed boundaries: ${sanitizeForLog(dir)}`);
|
|
350
|
+
}
|
|
351
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
352
|
+
fs.mkdirSync(resolvedDir, { recursive: true });
|
|
267
353
|
}
|
|
268
354
|
const projectSlug = getProjectName(context.projectPath).toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
269
355
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
270
356
|
const filename = `${projectSlug}-${date}.md`;
|
|
271
|
-
const filepath =
|
|
357
|
+
const filepath = path2.join(resolvedDir, filename);
|
|
358
|
+
if (!isPathWithinAllowedBoundaries(filepath)) {
|
|
359
|
+
throw new Error(`Output path outside allowed boundaries: ${sanitizeForLog(filepath)}`);
|
|
360
|
+
}
|
|
272
361
|
const content = formatRecoveredContextAsMarkdown(context);
|
|
273
362
|
fs.writeFileSync(filepath, content, "utf-8");
|
|
274
363
|
return filepath;
|
|
@@ -313,6 +402,10 @@ function formatDate(date) {
|
|
|
313
402
|
}
|
|
314
403
|
|
|
315
404
|
export {
|
|
405
|
+
isValidSessionId,
|
|
406
|
+
isValidProjectPath,
|
|
407
|
+
maskSecret,
|
|
408
|
+
sanitizeForLog,
|
|
316
409
|
getClaudeProjectsPath,
|
|
317
410
|
encodeProjectPath,
|
|
318
411
|
decodeProjectPath,
|