@vtstech/pi-shared 1.1.4 → 1.1.5

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/README.md CHANGED
@@ -10,7 +10,7 @@ This is an internal dependency — you don't need to install it directly. It's p
10
10
  |--------|-------------|
11
11
  | `format` | Section headers, indicators (ok/fail/warn/info), numeric formatters (bytes, ms, percentages), string utilities |
12
12
  | `ollama` | Ollama base URL resolution, models.json I/O with TTL cache, model family detection, provider detection, Ollama API helpers |
13
- | `security` | Command blocklist (65), SSRF patterns (29), path validation with symlink dereference, URL validation, command sanitization, audit logging (`AUDIT_LOG_PATH` exported) |
13
+ | `security` | Security mode toggle (`basic`/`max`), partitioned command blocklist (41 CRITICAL + 25 EXTENDED), mode-aware SSRF (19 + 7 patterns), path validation with symlink dereference, URL validation, command sanitization, audit logging with mode tracking (`AUDIT_LOG_PATH` exported) |
14
14
  | `types` | Type definitions (ToolSupportLevel, AuditEntry, etc.) |
15
15
 
16
16
  ## Usage
@@ -27,4 +27,4 @@ import { readModelsJson, getOllamaBaseUrl } from "@vtstech/pi-shared/ollama";
27
27
 
28
28
  ## License
29
29
 
30
- MIT — [VTSTech](https://www.vts-tech.org)
30
+ MIT — [VTSTech](https://www.vts-tech.org)
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.1.4";
15
+ var EXTENSION_VERSION = "1.1.5";
16
16
  var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
17
17
  var _modelsJsonCache = null;
18
18
  var _ollamaBaseUrlCache = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtstech/pi-shared",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Shared utilities for Pi Coding Agent extensions",
5
5
  "exports": {
6
6
  "./format": "./format.js",
package/security.js CHANGED
@@ -13,53 +13,69 @@ function debugLog(module, message, ...args) {
13
13
 
14
14
  // shared/security.ts
15
15
  var SETTINGS_PATH = path.join(os.homedir(), ".pi", "agent", "settings.json");
16
- var BLOCKED_COMMANDS = /* @__PURE__ */ new Set([
17
- // System modification
18
- "rm",
19
- "rmdir",
20
- "del",
21
- "format",
22
- "fdisk",
16
+ var SECURITY_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "security.json");
17
+ function getSecurityMode() {
18
+ try {
19
+ if (!fs.existsSync(SECURITY_CONFIG_PATH)) return "max";
20
+ const raw = fs.readFileSync(SECURITY_CONFIG_PATH, "utf-8");
21
+ const config = JSON.parse(raw);
22
+ if (config.mode === "basic" || config.mode === "max") return config.mode;
23
+ return "max";
24
+ } catch (err) {
25
+ debugLog("security", `failed to read security config at ${SECURITY_CONFIG_PATH}`, err);
26
+ return "max";
27
+ }
28
+ }
29
+ function setSecurityMode(mode) {
30
+ const configDir = path.dirname(SECURITY_CONFIG_PATH);
31
+ try {
32
+ if (!fs.existsSync(configDir)) {
33
+ fs.mkdirSync(configDir, { recursive: true });
34
+ }
35
+ const config = { mode, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
36
+ fs.writeFileSync(SECURITY_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
37
+ const verify = JSON.parse(fs.readFileSync(SECURITY_CONFIG_PATH, "utf-8"));
38
+ if (verify.mode !== mode) {
39
+ debugLog("security", `security config write verification failed: expected ${mode}, got ${verify.mode}`);
40
+ return false;
41
+ }
42
+ debugLog("security", `security mode set to ${mode}`, { path: SECURITY_CONFIG_PATH });
43
+ return true;
44
+ } catch (err) {
45
+ debugLog("security", `failed to write security config to ${SECURITY_CONFIG_PATH}`, err);
46
+ return false;
47
+ }
48
+ }
49
+ var CRITICAL_COMMANDS = /* @__PURE__ */ new Set([
50
+ // Filesystem destruction (irrecoverable)
23
51
  "mkfs",
24
52
  "dd",
25
53
  "shred",
26
54
  "wipe",
27
55
  "srm",
28
- // Privilege escalation
29
- "sudo",
56
+ "format",
57
+ "fdisk",
58
+ // Privilege escalation (non-sudo)
30
59
  "su",
31
60
  "doas",
32
61
  "pkexec",
33
62
  "gksudo",
34
63
  "kdesu",
35
- // Network attacks
64
+ // Network attack tools
36
65
  "nmap",
37
66
  "nc",
38
67
  "netcat",
39
68
  "telnet",
40
- "wget",
41
- "curl",
69
+ // Remote access
42
70
  "ssh",
43
71
  "scp",
44
72
  "sftp",
45
73
  "rsync",
46
- // Package management
47
- "apt",
48
- "apt-get",
49
- "yum",
50
- "dnf",
51
- "pacman",
52
- "pip",
53
- "npm",
54
- "yarn",
55
- "cargo",
56
- // Process control
74
+ // Process killing
57
75
  "kill",
58
76
  "killall",
59
77
  "pkill",
60
78
  "xkill",
61
- "systemctl",
62
- "service",
63
79
  // User management
64
80
  "useradd",
65
81
  "userdel",
@@ -73,30 +89,56 @@ var BLOCKED_COMMANDS = /* @__PURE__ */ new Set([
73
89
  "source",
74
90
  ".",
75
91
  "alias",
76
- // Filesystem
92
+ // Filesystem control
77
93
  "mount",
78
94
  "umount",
79
- "chown",
80
- "chmod",
81
95
  "chattr",
82
96
  "lsattr",
83
- // Shell escapes
97
+ // Permission modification
98
+ "chown",
99
+ "chmod"
100
+ ]);
101
+ var EXTENDED_COMMANDS = /* @__PURE__ */ new Set([
102
+ // File deletion
103
+ "rm",
104
+ "rmdir",
105
+ "del",
106
+ // Privilege escalation
107
+ "sudo",
108
+ // Download tools
109
+ "wget",
110
+ "curl",
111
+ // Package management
112
+ "apt",
113
+ "apt-get",
114
+ "yum",
115
+ "dnf",
116
+ "pacman",
117
+ "pip",
118
+ "npm",
119
+ "yarn",
120
+ "cargo",
121
+ // System service control
122
+ "systemctl",
123
+ "service",
124
+ // Interactive editors (shell escape risk)
84
125
  "vi",
85
126
  "vim",
86
127
  "nano",
87
128
  "emacs",
88
129
  "less",
89
130
  "more",
90
- "man"
131
+ "man",
132
+ // Version control
133
+ "git"
91
134
  ]);
92
- var BLOCKED_URL_PATTERNS = /* @__PURE__ */ new Set([
93
- // Loopback (full 127.0.0.0/8 range)
94
- "localhost",
95
- "127.",
96
- "0.0.0.0",
97
- "::1",
98
- "::ffff:127.0.0.1",
99
- "::ffff:0.0.0.0",
135
+ var BLOCKED_COMMANDS = /* @__PURE__ */ new Set([
136
+ ...CRITICAL_COMMANDS,
137
+ ...EXTENDED_COMMANDS
138
+ ]);
139
+ var BLOCKED_URL_ALWAYS = /* @__PURE__ */ new Set([
140
+ // Cloud metadata endpoints
141
+ "169.254.169.254",
100
142
  // RFC1918 private ranges
101
143
  "10.",
102
144
  "192.168.",
@@ -116,14 +158,26 @@ var BLOCKED_URL_PATTERNS = /* @__PURE__ */ new Set([
116
158
  "172.29.",
117
159
  "172.30.",
118
160
  "172.31.",
119
- // Cloud metadata endpoints
120
- "169.254.169.254",
121
161
  // Internal service patterns
122
162
  "internal.",
123
- "local.",
124
163
  "private.",
125
164
  "intranet."
126
165
  ]);
166
+ var BLOCKED_URL_MAX_ONLY = /* @__PURE__ */ new Set([
167
+ // Loopback addresses (full 127.0.0.0/8 range)
168
+ "localhost",
169
+ "127.",
170
+ "0.0.0.0",
171
+ "::1",
172
+ "::ffff:127.0.0.1",
173
+ "::ffff:0.0.0.0",
174
+ // Local/internal patterns
175
+ "local."
176
+ ]);
177
+ var BLOCKED_URL_PATTERNS = /* @__PURE__ */ new Set([
178
+ ...BLOCKED_URL_ALWAYS,
179
+ ...BLOCKED_URL_MAX_ONLY
180
+ ]);
127
181
  var CRITICAL_SYSTEM_DIRS = [
128
182
  "/etc",
129
183
  "/root",
@@ -166,7 +220,8 @@ function validatePath(filePath, allowedDirs) {
166
220
  "/.gnupg/",
167
221
  path.join(os.homedir(), ".ssh"),
168
222
  path.join(os.homedir(), ".gnupg"),
169
- SETTINGS_PATH
223
+ SETTINGS_PATH,
224
+ SECURITY_CONFIG_PATH
170
225
  // NOTE: models.json is intentionally excluded from sensitivePaths.
171
226
  // Extensions use readModelsJson()/writeModelsJson() from shared/ollama.ts
172
227
  // for direct file I/O — not via Pi's tool system — so blocking it here
@@ -218,11 +273,19 @@ function isSafeUrl(url, blockSsrf = true) {
218
273
  return { safe: false, error: "URL hostname uses non-decimal IP format" };
219
274
  }
220
275
  if (blockSsrf) {
221
- for (const pattern of BLOCKED_URL_PATTERNS) {
276
+ const mode = getSecurityMode();
277
+ for (const pattern of BLOCKED_URL_ALWAYS) {
222
278
  if (normalized === pattern || normalized.endsWith("." + pattern) || normalized.startsWith(pattern)) {
223
279
  return { safe: false, error: `SSRF protection: blocked hostname pattern '${pattern}'` };
224
280
  }
225
281
  }
282
+ if (mode === "max") {
283
+ for (const pattern of BLOCKED_URL_MAX_ONLY) {
284
+ if (normalized === pattern || normalized.endsWith("." + pattern) || normalized.startsWith(pattern)) {
285
+ return { safe: false, error: `SSRF protection: blocked hostname pattern '${pattern}' (max mode)` };
286
+ }
287
+ }
288
+ }
226
289
  }
227
290
  return { safe: true, error: "" };
228
291
  }
@@ -249,8 +312,12 @@ function sanitizeCommand(command) {
249
312
  let baseCmd = parts[0].toLowerCase();
250
313
  if (baseCmd.includes("/")) baseCmd = baseCmd.split("/").pop();
251
314
  if (baseCmd.includes("\\")) baseCmd = baseCmd.split("\\").pop();
252
- if (BLOCKED_COMMANDS.has(baseCmd)) {
253
- return { isSafe: false, error: `Blocked command: ${baseCmd}`, command: "" };
315
+ if (CRITICAL_COMMANDS.has(baseCmd)) {
316
+ return { isSafe: false, error: `Blocked command: ${baseCmd} (critical)`, command: "" };
317
+ }
318
+ const mode = getSecurityMode();
319
+ if (mode === "max" && EXTENDED_COMMANDS.has(baseCmd)) {
320
+ return { isSafe: false, error: `Blocked command: ${baseCmd} (max mode)`, command: "" };
254
321
  }
255
322
  const stripped = command.replace(/\n/g, " ").replace(/\r/g, " ");
256
323
  if (stripped !== command) {
@@ -270,7 +337,8 @@ function appendAuditEntry(entry) {
270
337
  if (!fs.existsSync(AUDIT_DIR)) {
271
338
  fs.mkdirSync(AUDIT_DIR, { recursive: true });
272
339
  }
273
- const line = JSON.stringify(entry) + "\n";
340
+ const enriched = { ...entry, securityMode: getSecurityMode() };
341
+ const line = JSON.stringify(enriched) + "\n";
274
342
  fs.appendFileSync(AUDIT_LOG_PATH, line, "utf-8");
275
343
  } catch (err) {
276
344
  debugLog("security", "audit log write failure", err);
@@ -352,15 +420,22 @@ function checkInjectionPatterns(input) {
352
420
  export {
353
421
  AUDIT_LOG_PATH,
354
422
  BLOCKED_COMMANDS,
423
+ BLOCKED_URL_ALWAYS,
424
+ BLOCKED_URL_MAX_ONLY,
355
425
  BLOCKED_URL_PATTERNS,
426
+ CRITICAL_COMMANDS,
427
+ EXTENDED_COMMANDS,
428
+ SECURITY_CONFIG_PATH,
356
429
  SETTINGS_PATH,
357
430
  appendAuditEntry,
358
431
  checkBashToolInput,
359
432
  checkFileToolInput,
360
433
  checkHttpToolInput,
361
434
  checkInjectionPatterns,
435
+ getSecurityMode,
362
436
  isSafeUrl,
363
437
  readRecentAuditEntries,
364
438
  sanitizeCommand,
439
+ setSecurityMode,
365
440
  validatePath
366
441
  };