@vtstech/pi-shared 1.2.0 → 1.2.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/ollama.js +4 -4
- package/package.json +3 -2
- package/security.js +64 -30
- package/test-report.js +1 -1
package/ollama.js
CHANGED
|
@@ -12,7 +12,7 @@ function debugLog(module, message, ...args) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// shared/ollama.ts
|
|
15
|
-
var EXTENSION_VERSION = "1.2.
|
|
15
|
+
var EXTENSION_VERSION = "1.2.2";
|
|
16
16
|
var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
17
17
|
var _modelsJsonCache = null;
|
|
18
18
|
var _ollamaBaseUrlCache = null;
|
|
@@ -249,13 +249,13 @@ function detectModelFamily(modelName) {
|
|
|
249
249
|
}
|
|
250
250
|
return "unknown";
|
|
251
251
|
}
|
|
252
|
-
function detectProvider(ctx) {
|
|
252
|
+
function detectProvider(ctx, modelsJson) {
|
|
253
253
|
const model = ctx.model;
|
|
254
254
|
if (!model) return { kind: "unknown", name: "none" };
|
|
255
255
|
const providerName = model.provider || "";
|
|
256
256
|
if (!providerName) return { kind: "unknown", name: "none" };
|
|
257
|
-
const
|
|
258
|
-
const userProviderCfg = (
|
|
257
|
+
const effectiveModelsJson = modelsJson ?? readModelsJson();
|
|
258
|
+
const userProviderCfg = (effectiveModelsJson.providers || {})[providerName];
|
|
259
259
|
if (userProviderCfg) {
|
|
260
260
|
const baseUrl = userProviderCfg.baseUrl || "";
|
|
261
261
|
const apiMode = userProviderCfg.api || "";
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtstech/pi-shared",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Shared utilities for Pi Coding Agent extensions",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./config-io": "./config-io.js",
|
|
7
7
|
"./debug": "./debug.js",
|
|
8
|
+
"./errors": "./errors.js",
|
|
8
9
|
"./format": "./format.js",
|
|
9
10
|
"./model-test-utils": "./model-test-utils.js",
|
|
10
11
|
"./ollama": "./ollama.js",
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
"./test-report": "./test-report.js",
|
|
15
16
|
"./types": "./types.js"
|
|
16
17
|
},
|
|
17
|
-
"keywords": ["pi-extensions"],
|
|
18
|
+
"keywords": ["pi-package", "pi", "pi-coding-agent", "pi-extensions"],
|
|
18
19
|
"license": "MIT",
|
|
19
20
|
"access": "public",
|
|
20
21
|
"type": "module",
|
package/security.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// shared/security.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as fs2 from "node:fs";
|
|
3
3
|
import * as path2 from "node:path";
|
|
4
4
|
import os2 from "node:os";
|
|
5
5
|
|
|
@@ -15,9 +15,22 @@ function debugLog(module, message, ...args) {
|
|
|
15
15
|
import dns from "node:dns";
|
|
16
16
|
|
|
17
17
|
// shared/config-io.ts
|
|
18
|
+
import * as fs from "node:fs";
|
|
18
19
|
import * as path from "node:path";
|
|
19
20
|
import os from "node:os";
|
|
20
21
|
var PI_AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
22
|
+
function writeJsonConfig(filePath, data) {
|
|
23
|
+
const dir = path.dirname(filePath);
|
|
24
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
const content = JSON.stringify(data, null, 2) + "\n";
|
|
26
|
+
const tmpPath = filePath + ".tmp";
|
|
27
|
+
try {
|
|
28
|
+
fs.writeFileSync(tmpPath, content, "utf-8");
|
|
29
|
+
fs.renameSync(tmpPath, filePath);
|
|
30
|
+
} catch {
|
|
31
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
21
34
|
var SETTINGS_PATH = path.join(PI_AGENT_DIR, "settings.json");
|
|
22
35
|
var SECURITY_PATH = path.join(PI_AGENT_DIR, "security.json");
|
|
23
36
|
var REACT_MODE_PATH = path.join(PI_AGENT_DIR, "react-mode.json");
|
|
@@ -26,27 +39,42 @@ var MODEL_TEST_CONFIG_PATH = path.join(PI_AGENT_DIR, "model-test-config.json");
|
|
|
26
39
|
// shared/security.ts
|
|
27
40
|
var SETTINGS_PATH2 = SETTINGS_PATH;
|
|
28
41
|
var SECURITY_CONFIG_PATH = SECURITY_PATH;
|
|
42
|
+
var securityModeCache = null;
|
|
43
|
+
var securityModeCacheTime = 0;
|
|
44
|
+
var SECURITY_CACHE_DURATION_MS = 3e4;
|
|
29
45
|
function getSecurityMode() {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
if (securityModeCache && now - securityModeCacheTime < SECURITY_CACHE_DURATION_MS) {
|
|
48
|
+
return securityModeCache;
|
|
49
|
+
}
|
|
30
50
|
try {
|
|
31
|
-
if (!
|
|
32
|
-
|
|
51
|
+
if (!fs2.existsSync(SECURITY_CONFIG_PATH)) {
|
|
52
|
+
securityModeCache = "max";
|
|
53
|
+
securityModeCacheTime = now;
|
|
54
|
+
return "max";
|
|
55
|
+
}
|
|
56
|
+
const raw = fs2.readFileSync(SECURITY_CONFIG_PATH, "utf-8");
|
|
33
57
|
const config = JSON.parse(raw);
|
|
34
|
-
if (config.mode === "basic" || config.mode === "max"
|
|
58
|
+
if (config.mode === "basic" || config.mode === "max" || config.mode === "off") {
|
|
59
|
+
securityModeCache = config.mode;
|
|
60
|
+
securityModeCacheTime = now;
|
|
61
|
+
return config.mode;
|
|
62
|
+
}
|
|
63
|
+
securityModeCache = "max";
|
|
64
|
+
securityModeCacheTime = now;
|
|
35
65
|
return "max";
|
|
36
66
|
} catch (err) {
|
|
37
67
|
debugLog("security", `failed to read security config at ${SECURITY_CONFIG_PATH}`, err);
|
|
68
|
+
securityModeCache = "max";
|
|
69
|
+
securityModeCacheTime = now;
|
|
38
70
|
return "max";
|
|
39
71
|
}
|
|
40
72
|
}
|
|
41
73
|
function setSecurityMode(mode) {
|
|
42
|
-
const configDir = path2.dirname(SECURITY_CONFIG_PATH);
|
|
43
74
|
try {
|
|
44
|
-
if (!fs.existsSync(configDir)) {
|
|
45
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
46
|
-
}
|
|
47
75
|
const config = { mode, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
48
|
-
|
|
49
|
-
const verify = JSON.parse(
|
|
76
|
+
writeJsonConfig(SECURITY_CONFIG_PATH, config);
|
|
77
|
+
const verify = JSON.parse(fs2.readFileSync(SECURITY_CONFIG_PATH, "utf-8"));
|
|
50
78
|
if (verify.mode !== mode) {
|
|
51
79
|
debugLog("security", `security config write verification failed: expected ${mode}, got ${verify.mode}`);
|
|
52
80
|
return false;
|
|
@@ -235,7 +263,7 @@ function validatePath(filePath, allowedDirs) {
|
|
|
235
263
|
try {
|
|
236
264
|
resolved = path2.resolve(filePath);
|
|
237
265
|
try {
|
|
238
|
-
resolved =
|
|
266
|
+
resolved = fs2.realpathSync(resolved);
|
|
239
267
|
} catch {
|
|
240
268
|
}
|
|
241
269
|
} catch {
|
|
@@ -328,7 +356,7 @@ async function resolveAndCheckHostname(hostname, blockPrivate = true) {
|
|
|
328
356
|
return { safe: true, error: "" };
|
|
329
357
|
}
|
|
330
358
|
}
|
|
331
|
-
function isSafeUrl(url, blockSsrf = true) {
|
|
359
|
+
function isSafeUrl(url, blockSsrf = true, mode = "max") {
|
|
332
360
|
if (!url) return { safe: false, error: "URL cannot be empty" };
|
|
333
361
|
let parsed;
|
|
334
362
|
try {
|
|
@@ -353,7 +381,9 @@ function isSafeUrl(url, blockSsrf = true) {
|
|
|
353
381
|
return { safe: false, error: "URL hostname uses non-decimal IP format" };
|
|
354
382
|
}
|
|
355
383
|
if (blockSsrf) {
|
|
356
|
-
|
|
384
|
+
if (mode === "off") {
|
|
385
|
+
return { safe: true, error: "" };
|
|
386
|
+
}
|
|
357
387
|
for (const pattern of BLOCKED_URL_ALWAYS) {
|
|
358
388
|
if (normalized === pattern || normalized.endsWith("." + pattern) || normalized.startsWith(pattern)) {
|
|
359
389
|
if (/^\d|^::/.test(pattern)) {
|
|
@@ -477,11 +507,11 @@ function ensureAuditFlushTimer() {
|
|
|
477
507
|
function flushAuditBuffer() {
|
|
478
508
|
if (_auditBuffer.length === 0) return;
|
|
479
509
|
try {
|
|
480
|
-
if (!
|
|
481
|
-
|
|
510
|
+
if (!fs2.existsSync(AUDIT_DIR)) {
|
|
511
|
+
fs2.mkdirSync(AUDIT_DIR, { recursive: true });
|
|
482
512
|
}
|
|
483
513
|
const batch = _auditBuffer.join("");
|
|
484
|
-
|
|
514
|
+
fs2.appendFileSync(AUDIT_LOG_PATH, batch, "utf-8");
|
|
485
515
|
} catch (err) {
|
|
486
516
|
debugLog("security", "audit buffer flush failure", err);
|
|
487
517
|
}
|
|
@@ -492,12 +522,12 @@ function appendAuditEntry(entry) {
|
|
|
492
522
|
ensureAuditFlushTimer();
|
|
493
523
|
const AUDIT_LOG_MAX_SIZE = 5 * 1024 * 1024;
|
|
494
524
|
try {
|
|
495
|
-
if (
|
|
496
|
-
const stat =
|
|
525
|
+
if (fs2.existsSync(AUDIT_LOG_PATH)) {
|
|
526
|
+
const stat = fs2.statSync(AUDIT_LOG_PATH);
|
|
497
527
|
if (stat.size > AUDIT_LOG_MAX_SIZE) {
|
|
498
528
|
const entries = readRecentAuditEntries(1e3);
|
|
499
529
|
const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
500
|
-
|
|
530
|
+
fs2.writeFileSync(AUDIT_LOG_PATH, content, "utf-8");
|
|
501
531
|
}
|
|
502
532
|
}
|
|
503
533
|
} catch (err) {
|
|
@@ -515,10 +545,10 @@ function appendAuditEntry(entry) {
|
|
|
515
545
|
}
|
|
516
546
|
function readRecentAuditEntries(count = 50) {
|
|
517
547
|
try {
|
|
518
|
-
if (!
|
|
519
|
-
const fileSize =
|
|
548
|
+
if (!fs2.existsSync(AUDIT_LOG_PATH)) return [];
|
|
549
|
+
const fileSize = fs2.statSync(AUDIT_LOG_PATH).size;
|
|
520
550
|
if (fileSize === 0) return [];
|
|
521
|
-
const fd =
|
|
551
|
+
const fd = fs2.openSync(AUDIT_LOG_PATH, "r");
|
|
522
552
|
const bufferSize = 8192;
|
|
523
553
|
const buffer = Buffer.alloc(bufferSize);
|
|
524
554
|
const lines = [];
|
|
@@ -527,7 +557,7 @@ function readRecentAuditEntries(count = 50) {
|
|
|
527
557
|
while (pos > 0 && lines.length < count) {
|
|
528
558
|
const readSize = Math.min(bufferSize, pos);
|
|
529
559
|
pos -= readSize;
|
|
530
|
-
|
|
560
|
+
fs2.readSync(fd, buffer, 0, readSize, pos);
|
|
531
561
|
const chunk = buffer.slice(0, readSize).toString("utf-8");
|
|
532
562
|
partial = chunk + partial;
|
|
533
563
|
const lineBreak = partial.lastIndexOf("\n");
|
|
@@ -537,7 +567,7 @@ function readRecentAuditEntries(count = 50) {
|
|
|
537
567
|
partial = partial.slice(0, lineBreak);
|
|
538
568
|
}
|
|
539
569
|
}
|
|
540
|
-
|
|
570
|
+
fs2.closeSync(fd);
|
|
541
571
|
if (partial.trim() && lines.length < count) {
|
|
542
572
|
lines.unshift(partial);
|
|
543
573
|
}
|
|
@@ -560,16 +590,18 @@ process.on("exit", () => {
|
|
|
560
590
|
process.on("SIGTERM", () => {
|
|
561
591
|
flushAuditBuffer();
|
|
562
592
|
});
|
|
563
|
-
function checkBashToolInput(input) {
|
|
593
|
+
function checkBashToolInput(input, mode = "max") {
|
|
594
|
+
if (mode === "off") return { safe: true, rule: "", detail: "" };
|
|
564
595
|
const command = input.command ?? input.cmd ?? "";
|
|
565
596
|
if (!command) return { safe: true, rule: "", detail: "" };
|
|
566
|
-
const result = sanitizeCommand(command);
|
|
597
|
+
const result = sanitizeCommand(command, mode);
|
|
567
598
|
if (!result.isSafe) {
|
|
568
599
|
return { safe: false, rule: "command_blocklist", detail: result.error };
|
|
569
600
|
}
|
|
570
601
|
return { safe: true, rule: "", detail: "" };
|
|
571
602
|
}
|
|
572
|
-
function checkFileToolInput(input) {
|
|
603
|
+
function checkFileToolInput(input, mode = "max") {
|
|
604
|
+
if (mode === "off") return { safe: true, rule: "", detail: "" };
|
|
573
605
|
const filePaths = [
|
|
574
606
|
input.file_path,
|
|
575
607
|
input.path,
|
|
@@ -585,16 +617,18 @@ function checkFileToolInput(input) {
|
|
|
585
617
|
}
|
|
586
618
|
return { safe: true, rule: "", detail: "" };
|
|
587
619
|
}
|
|
588
|
-
function checkHttpToolInput(input) {
|
|
620
|
+
function checkHttpToolInput(input, mode = "max") {
|
|
621
|
+
if (mode === "off") return { safe: true, rule: "", detail: "" };
|
|
589
622
|
const url = input.url ?? input.uri ?? input.endpoint ?? "";
|
|
590
623
|
if (!url) return { safe: true, rule: "", detail: "" };
|
|
591
|
-
const result = isSafeUrl(url);
|
|
624
|
+
const result = isSafeUrl(url, mode);
|
|
592
625
|
if (!result.safe) {
|
|
593
626
|
return { safe: false, rule: "ssrf_protection", detail: result.error };
|
|
594
627
|
}
|
|
595
628
|
return { safe: true, rule: "", detail: "" };
|
|
596
629
|
}
|
|
597
|
-
function checkInjectionPatterns(input) {
|
|
630
|
+
function checkInjectionPatterns(input, mode = "max") {
|
|
631
|
+
if (mode === "off") return { safe: true, rule: "", detail: "" };
|
|
598
632
|
const dangerousPatterns = [
|
|
599
633
|
/;\s*(rm|sudo|chmod|chown|mkfs|dd|shred)\b/,
|
|
600
634
|
/\|\s*(rm|sudo|chmod|shred|mkfs)\b/,
|
package/test-report.js
CHANGED
|
@@ -29,7 +29,7 @@ import os from "node:os";
|
|
|
29
29
|
var DEBUG_ENABLED = process.env.PI_EXTENSIONS_DEBUG === "1";
|
|
30
30
|
|
|
31
31
|
// shared/ollama.ts
|
|
32
|
-
var EXTENSION_VERSION = "1.2.
|
|
32
|
+
var EXTENSION_VERSION = "1.2.2";
|
|
33
33
|
var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
34
34
|
|
|
35
35
|
// shared/test-report.ts
|