shiva-code 0.4.3 → 0.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/dist/{api-MT23KCO3.js → api-OEHQTBH7.js} +2 -2
- package/dist/{chunk-TI6Y3VT4.js → chunk-H5OFO4VS.js} +155 -27
- package/dist/{chunk-6RAACMKF.js → chunk-HIQO2DBA.js} +2 -2
- package/dist/{chunk-ZDLLPNCK.js → chunk-IVFCZLBX.js} +1 -1
- package/dist/{chunk-TQ6O4QB6.js → chunk-LBTCSQAX.js} +1 -1
- package/dist/index.js +987 -708
- package/dist/{manager-TQIASXKY.js → manager-ZPQWG7E6.js} +8 -2
- package/dist/{package-manager-NARG55B5.js → package-manager-NHNQATBH.js} +3 -3
- package/package.json +1 -1
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
parseGitHubUrl,
|
|
22
22
|
runGh,
|
|
23
23
|
runGhRaw
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-IVFCZLBX.js";
|
|
25
|
+
import "./chunk-HIQO2DBA.js";
|
|
26
26
|
import "./chunk-3RG5ZIWI.js";
|
|
27
27
|
export {
|
|
28
28
|
findMentionedIssueNumbers,
|
|
@@ -1,14 +1,90 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cache
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HIQO2DBA.js";
|
|
4
4
|
|
|
5
5
|
// src/services/session/manager.ts
|
|
6
6
|
import * as fs from "fs";
|
|
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
|
|
7
13
|
import * as path from "path";
|
|
8
14
|
import * as os from "os";
|
|
9
|
-
|
|
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
|
|
10
86
|
function getClaudeProjectsPath() {
|
|
11
|
-
return
|
|
87
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
12
88
|
}
|
|
13
89
|
function encodeProjectPath(projectPath) {
|
|
14
90
|
return projectPath.replace(/\//g, "-").replace(/^-/, "");
|
|
@@ -18,7 +94,7 @@ function decodeProjectPath(encoded) {
|
|
|
18
94
|
return "/" + parts.join("/");
|
|
19
95
|
}
|
|
20
96
|
function getProjectName(projectPath) {
|
|
21
|
-
return
|
|
97
|
+
return path2.basename(projectPath);
|
|
22
98
|
}
|
|
23
99
|
async function getAllClaudeProjects(skipCache = false) {
|
|
24
100
|
if (!skipCache) {
|
|
@@ -31,26 +107,25 @@ async function getAllClaudeProjects(skipCache = false) {
|
|
|
31
107
|
if (!fs.existsSync(projectsPath)) {
|
|
32
108
|
return [];
|
|
33
109
|
}
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
for (const entry of entries) {
|
|
37
|
-
if (!entry.isDirectory()) continue;
|
|
110
|
+
const entries = await fsPromises.readdir(projectsPath, { withFileTypes: true });
|
|
111
|
+
const projectPromises = entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
38
112
|
const encodedPath = entry.name;
|
|
39
113
|
const absolutePath = decodeProjectPath(encodedPath);
|
|
40
114
|
const projectName = getProjectName(absolutePath);
|
|
41
|
-
const sessionIndex =
|
|
115
|
+
const sessionIndex = await getSessionIndexAsync(encodedPath);
|
|
42
116
|
const sessions = sessionIndex?.entries || [];
|
|
43
117
|
sessions.sort(
|
|
44
118
|
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
|
|
45
119
|
);
|
|
46
|
-
|
|
120
|
+
return {
|
|
47
121
|
encodedPath,
|
|
48
122
|
absolutePath,
|
|
49
123
|
projectName,
|
|
50
124
|
sessions,
|
|
51
125
|
latestSession: sessions[0]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
const projects = await Promise.all(projectPromises);
|
|
54
129
|
projects.sort((a, b) => {
|
|
55
130
|
const aTime = a.latestSession ? new Date(a.latestSession.modified).getTime() : 0;
|
|
56
131
|
const bTime = b.latestSession ? new Date(b.latestSession.modified).getTime() : 0;
|
|
@@ -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
|
}
|
|
@@ -78,9 +154,33 @@ function getSessionIndex(encodedPath) {
|
|
|
78
154
|
return null;
|
|
79
155
|
}
|
|
80
156
|
}
|
|
81
|
-
async function
|
|
82
|
-
const
|
|
83
|
-
const
|
|
157
|
+
async function getSessionIndexAsync(encodedPath) {
|
|
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
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const content = await fsPromises.readFile(indexPath, "utf-8");
|
|
166
|
+
return JSON.parse(content);
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function findProject(nameOrPath, useCache = true) {
|
|
172
|
+
const projects = await getAllClaudeProjects(!useCache);
|
|
173
|
+
const absolutePath = path2.resolve(nameOrPath);
|
|
174
|
+
const byPath = projects.find((p) => p.absolutePath === absolutePath);
|
|
175
|
+
if (byPath) return byPath;
|
|
176
|
+
const lowerName = nameOrPath.toLowerCase();
|
|
177
|
+
const byName = projects.find(
|
|
178
|
+
(p) => p.projectName.toLowerCase() === lowerName || p.projectName.toLowerCase().includes(lowerName)
|
|
179
|
+
);
|
|
180
|
+
return byName || null;
|
|
181
|
+
}
|
|
182
|
+
function findProjectFromArray(projects, nameOrPath) {
|
|
183
|
+
const absolutePath = path2.resolve(nameOrPath);
|
|
84
184
|
const byPath = projects.find((p) => p.absolutePath === absolutePath);
|
|
85
185
|
if (byPath) return byPath;
|
|
86
186
|
const lowerName = nameOrPath.toLowerCase();
|
|
@@ -103,6 +203,15 @@ async function findProjectForCurrentDir() {
|
|
|
103
203
|
function getSessionFilePath(session) {
|
|
104
204
|
return session.fullPath;
|
|
105
205
|
}
|
|
206
|
+
function isSessionCorruptedQuick(session) {
|
|
207
|
+
const filePath = getSessionFilePath(session);
|
|
208
|
+
try {
|
|
209
|
+
const stats = fs.statSync(filePath);
|
|
210
|
+
return stats.size > 100 * 1024 * 1024;
|
|
211
|
+
} catch {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
106
215
|
function isSessionCorrupted(session) {
|
|
107
216
|
const filePath = getSessionFilePath(session);
|
|
108
217
|
if (!fs.existsSync(filePath)) {
|
|
@@ -141,10 +250,15 @@ function isSessionActive(session) {
|
|
|
141
250
|
}
|
|
142
251
|
async function parseSessionFile(jsonlPath, level = "standard") {
|
|
143
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
|
+
}
|
|
144
258
|
if (!fs.existsSync(jsonlPath)) {
|
|
145
259
|
return summaries;
|
|
146
260
|
}
|
|
147
|
-
return new Promise((
|
|
261
|
+
return new Promise((resolve3) => {
|
|
148
262
|
const fileStream = fs.createReadStream(jsonlPath, { encoding: "utf-8" });
|
|
149
263
|
const rl = readline.createInterface({
|
|
150
264
|
input: fileStream,
|
|
@@ -181,10 +295,10 @@ async function parseSessionFile(jsonlPath, level = "standard") {
|
|
|
181
295
|
}
|
|
182
296
|
});
|
|
183
297
|
rl.on("close", () => {
|
|
184
|
-
|
|
298
|
+
resolve3(summaries);
|
|
185
299
|
});
|
|
186
300
|
rl.on("error", () => {
|
|
187
|
-
|
|
301
|
+
resolve3(summaries);
|
|
188
302
|
});
|
|
189
303
|
});
|
|
190
304
|
}
|
|
@@ -229,14 +343,21 @@ function formatRecoveredContextAsMarkdown(context) {
|
|
|
229
343
|
return lines.join("\n");
|
|
230
344
|
}
|
|
231
345
|
function saveRecoveredContext(context, outputDir) {
|
|
232
|
-
const dir = outputDir ||
|
|
233
|
-
|
|
234
|
-
|
|
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 });
|
|
235
353
|
}
|
|
236
354
|
const projectSlug = getProjectName(context.projectPath).toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
237
355
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
238
356
|
const filename = `${projectSlug}-${date}.md`;
|
|
239
|
-
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
|
+
}
|
|
240
361
|
const content = formatRecoveredContextAsMarkdown(context);
|
|
241
362
|
fs.writeFileSync(filepath, content, "utf-8");
|
|
242
363
|
return filepath;
|
|
@@ -281,6 +402,10 @@ function formatDate(date) {
|
|
|
281
402
|
}
|
|
282
403
|
|
|
283
404
|
export {
|
|
405
|
+
isValidSessionId,
|
|
406
|
+
isValidProjectPath,
|
|
407
|
+
maskSecret,
|
|
408
|
+
sanitizeForLog,
|
|
284
409
|
getClaudeProjectsPath,
|
|
285
410
|
encodeProjectPath,
|
|
286
411
|
decodeProjectPath,
|
|
@@ -288,10 +413,13 @@ export {
|
|
|
288
413
|
getAllClaudeProjects,
|
|
289
414
|
invalidateSessionsCache,
|
|
290
415
|
getSessionIndex,
|
|
416
|
+
getSessionIndexAsync,
|
|
291
417
|
findProject,
|
|
418
|
+
findProjectFromArray,
|
|
292
419
|
findSessionByBranch,
|
|
293
420
|
findProjectForCurrentDir,
|
|
294
421
|
getSessionFilePath,
|
|
422
|
+
isSessionCorruptedQuick,
|
|
295
423
|
isSessionCorrupted,
|
|
296
424
|
isSessionActive,
|
|
297
425
|
parseSessionFile,
|
|
@@ -3,8 +3,8 @@ var CacheService = class _CacheService {
|
|
|
3
3
|
cache;
|
|
4
4
|
// Default TTLs in milliseconds
|
|
5
5
|
static TTL = {
|
|
6
|
-
SESSIONS:
|
|
7
|
-
//
|
|
6
|
+
SESSIONS: 5 * 60 * 1e3,
|
|
7
|
+
// 5 minutes for sessions (optimized for better cache hit rate)
|
|
8
8
|
GITHUB_ISSUES: 5 * 60 * 1e3,
|
|
9
9
|
// 5 minutes for GitHub issues
|
|
10
10
|
GITHUB_PRS: 5 * 60 * 1e3,
|