@vtstech/pi-security 1.0.3

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.
Files changed (2) hide show
  1. package/package.json +24 -0
  2. package/security.js +233 -0
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@vtstech/pi-security",
3
+ "version": "1.0.3",
4
+ "description": "Security extension for Pi Coding Agent",
5
+ "main": "security.js",
6
+ "keywords": ["pi-package", "pi-extensions"],
7
+ "license": "MIT",
8
+ "access": "public",
9
+ "author": "VTSTech",
10
+ "homepage": "https://www.vts-tech.org",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/VTSTech/pi-coding-agent"
14
+ },
15
+ "dependencies": {
16
+ "@vtstech/pi-shared": "1.0.3"
17
+ },
18
+ "peerDependencies": {
19
+ "@mariozechner/pi-coding-agent": ">=0.66"
20
+ },
21
+ "pi": {
22
+ "extensions": ["./security.js"]
23
+ }
24
+ }
package/security.js ADDED
@@ -0,0 +1,233 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // .build-npm/security/security.temp.ts
20
+ var security_temp_exports = {};
21
+ __export(security_temp_exports, {
22
+ default: () => security_temp_default
23
+ });
24
+ module.exports = __toCommonJS(security_temp_exports);
25
+ var import_security = require("@vtstech/pi-shared/security");
26
+ var import_format = require("@vtstech/pi-shared/format");
27
+ function security_temp_default(pi) {
28
+ const stats = {
29
+ blocked: 0,
30
+ allowed: 0,
31
+ warnings: 0,
32
+ byRule: {}
33
+ };
34
+ const branding = [
35
+ ` \u26A1 Pi Security Extension v1.0.3`,
36
+ ` Written by VTSTech`,
37
+ ` GitHub: https://github.com/VTSTech`,
38
+ ` Website: www.vts-tech.org`
39
+ ].join("\n");
40
+ pi.on("tool_call", (event) => {
41
+ const toolName = event.toolName;
42
+ const input = event.input ?? {};
43
+ const toolCallId = event.toolCallId;
44
+ let result;
45
+ switch (toolName) {
46
+ case "bash":
47
+ case "shell":
48
+ case "run_command":
49
+ result = (0, import_security.checkBashToolInput)(input);
50
+ break;
51
+ case "read":
52
+ case "read_file":
53
+ case "write":
54
+ case "write_file":
55
+ case "edit":
56
+ case "edit_file":
57
+ case "list_directory":
58
+ case "list_dir":
59
+ result = (0, import_security.checkFileToolInput)(input);
60
+ break;
61
+ case "http_get":
62
+ case "http_post":
63
+ case "fetch":
64
+ case "web_search":
65
+ case "http_request":
66
+ result = (0, import_security.checkHttpToolInput)(input);
67
+ break;
68
+ default:
69
+ result = (0, import_security.checkInjectionPatterns)(input);
70
+ break;
71
+ }
72
+ if (!result.safe) {
73
+ stats.blocked++;
74
+ stats.byRule[result.rule] = (stats.byRule[result.rule] || 0) + 1;
75
+ stats.lastBlocked = {
76
+ tool: toolName,
77
+ rule: result.rule,
78
+ detail: result.detail,
79
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
80
+ };
81
+ (0, import_security.appendAuditEntry)({
82
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
83
+ toolName,
84
+ toolCallId,
85
+ action: "blocked",
86
+ rule: result.rule,
87
+ detail: result.detail,
88
+ input: sanitizeInputForLog(input)
89
+ });
90
+ return {
91
+ block: true,
92
+ reason: `[SECURITY] ${result.detail} (rule: ${result.rule})`
93
+ };
94
+ }
95
+ stats.allowed++;
96
+ if (["bash", "shell", "write", "write_file", "edit", "edit_file"].includes(toolName)) {
97
+ stats.warnings++;
98
+ (0, import_security.appendAuditEntry)({
99
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
100
+ toolName,
101
+ toolCallId,
102
+ action: "allowed",
103
+ rule: result.rule || "none",
104
+ detail: "Dangerous tool executed",
105
+ input: sanitizeInputForLog(input)
106
+ });
107
+ }
108
+ });
109
+ pi.on("tool_result", (event) => {
110
+ const toolName = event.toolName;
111
+ const isError = event.isError;
112
+ if (isError) {
113
+ (0, import_security.appendAuditEntry)({
114
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
115
+ toolName,
116
+ toolCallId: event.toolCallId,
117
+ action: "warning",
118
+ rule: "tool_error",
119
+ detail: "Tool execution failed",
120
+ input: sanitizeInputForLog(event.input)
121
+ });
122
+ }
123
+ });
124
+ async function generateAuditReport() {
125
+ const lines = [];
126
+ lines.push(branding);
127
+ lines.push((0, import_format.section)("SESSION STATISTICS"));
128
+ lines.push((0, import_format.info)(`Tool calls allowed: ${stats.allowed}`));
129
+ lines.push((0, import_format.info)(`Tool calls blocked: ${stats.blocked}`));
130
+ lines.push((0, import_format.info)(`Dangerous operations logged: ${stats.warnings}`));
131
+ if (stats.blocked > 0) {
132
+ lines.push((0, import_format.warn)(`${stats.blocked} operation(s) were blocked by security rules`));
133
+ }
134
+ const ruleNames = Object.keys(stats.byRule);
135
+ if (ruleNames.length > 0) {
136
+ lines.push((0, import_format.section)("BLOCKED BY RULE"));
137
+ for (const rule of ruleNames) {
138
+ lines.push((0, import_format.info)(` ${rule}: ${stats.byRule[rule]} blocked`));
139
+ }
140
+ }
141
+ if (stats.lastBlocked) {
142
+ lines.push((0, import_format.section)("LAST BLOCKED"));
143
+ lines.push((0, import_format.fail)(`Tool: ${stats.lastBlocked.tool}`));
144
+ lines.push((0, import_format.fail)(`Rule: ${stats.lastBlocked.rule}`));
145
+ lines.push((0, import_format.fail)(`Detail: ${stats.lastBlocked.detail}`));
146
+ lines.push((0, import_format.info)(`Time: ${stats.lastBlocked.timestamp}`));
147
+ }
148
+ lines.push((0, import_format.section)("SECURITY CONFIGURATION"));
149
+ lines.push((0, import_format.info)(`Blocked commands: ${import_security.BLOCKED_COMMANDS.size}`));
150
+ lines.push((0, import_format.info)(`Blocked URL patterns: ${import_security.BLOCKED_URL_PATTERNS.size}`));
151
+ lines.push((0, import_format.info)(`Active checks: command_blocklist, path_validation, ssrf_protection, injection_detection`));
152
+ const recentEntries = (0, import_security.readRecentAuditEntries)(20);
153
+ if (recentEntries.length > 0) {
154
+ lines.push((0, import_format.section)("RECENT AUDIT LOG (last 20)"));
155
+ for (const entry of recentEntries) {
156
+ const ts = entry.timestamp || "?";
157
+ const action = entry.action;
158
+ const tool = entry.toolName;
159
+ const rule = entry.rule;
160
+ const detail = entry.detail;
161
+ if (action === "blocked") {
162
+ lines.push((0, import_format.fail)(`[${ts}] ${tool} \u2192 BLOCKED (${rule}): ${detail}`));
163
+ } else if (action === "warning") {
164
+ lines.push((0, import_format.warn)(`[${ts}] ${tool} \u2192 WARNING (${rule}): ${detail}`));
165
+ } else {
166
+ lines.push((0, import_format.ok)(`[${ts}] ${tool} \u2192 allowed (${rule})`));
167
+ }
168
+ }
169
+ }
170
+ lines.push((0, import_format.section)("SUMMARY"));
171
+ if (stats.blocked === 0) {
172
+ lines.push((0, import_format.ok)("No security violations detected in this session"));
173
+ } else {
174
+ lines.push((0, import_format.fail)(`${stats.blocked} security violation(s) blocked`));
175
+ }
176
+ lines.push(branding);
177
+ return lines.join("\n");
178
+ }
179
+ pi.registerCommand("security-audit", {
180
+ description: "Show security audit report \u2014 blocked operations, stats, and recent log",
181
+ handler: async (_args, ctx) => {
182
+ if (!ctx.hasUI) {
183
+ ctx.ui.notify("Security audit requires TUI mode", "error");
184
+ return;
185
+ }
186
+ try {
187
+ const report = await generateAuditReport();
188
+ pi.sendMessage({
189
+ customType: "security-audit-report",
190
+ content: report,
191
+ display: { type: "content", content: report }
192
+ });
193
+ } catch (e) {
194
+ ctx.ui.notify(`Security audit failed: ${e.message}`, "error");
195
+ }
196
+ }
197
+ });
198
+ pi.registerTool({
199
+ name: "security_audit",
200
+ label: "Security Audit",
201
+ description: "Run a security audit showing blocked operations, security statistics, and recent audit log entries. Use this when the user asks about security status or wants to review security events.",
202
+ promptSnippet: "security_audit - show security status and blocked operations",
203
+ promptGuidelines: [
204
+ "When the user asks about security, blocked operations, or audit log, call security_audit."
205
+ ],
206
+ parameters: {},
207
+ execute: async (_toolCallId, _params, _signal, _onUpdate, _ctx) => {
208
+ try {
209
+ const report = await generateAuditReport();
210
+ return {
211
+ content: [{ type: "text", text: report }],
212
+ isError: false
213
+ };
214
+ } catch (e) {
215
+ return {
216
+ content: [{ type: "text", text: `Security audit failed: ${e.message}` }],
217
+ isError: true
218
+ };
219
+ }
220
+ }
221
+ });
222
+ }
223
+ function sanitizeInputForLog(input) {
224
+ const sanitized = {};
225
+ for (const [key, value] of Object.entries(input)) {
226
+ if (typeof value === "string" && value.length > 500) {
227
+ sanitized[key] = value.slice(0, 500) + "... (truncated)";
228
+ } else {
229
+ sanitized[key] = value;
230
+ }
231
+ }
232
+ return sanitized;
233
+ }