pi-jev-guard 0.1.0
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 +142 -0
- package/config/jev-config.example.json +61 -0
- package/extensions/index.ts +692 -0
- package/package.json +39 -0
- package/skills/jev-review/SKILL.md +25 -0
- package/src/adapters/openrouter.ts +289 -0
- package/src/adapters/typesafe.ts +244 -0
- package/src/ask.ts +189 -0
- package/src/automatic/gate.ts +45 -0
- package/src/automatic/guardian.ts +320 -0
- package/src/automatic/overlay.ts +247 -0
- package/src/automatic/recovery.ts +32 -0
- package/src/automatic/serialize.ts +278 -0
- package/src/cache.ts +56 -0
- package/src/commands.ts +408 -0
- package/src/config.ts +385 -0
- package/src/exfil.ts +180 -0
- package/src/factory.ts +140 -0
- package/src/markdown.ts +26 -0
- package/src/metrics.ts +69 -0
- package/src/output-judge.ts +176 -0
- package/src/policy.ts +32 -0
- package/src/reviewer.ts +350 -0
- package/src/tools-policy.ts +128 -0
- package/src/tools.ts +43 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy deterministica pre-esecuzione tool (zero costo, sincrona).
|
|
3
|
+
* Applicata solo in modalità automatic (on-demand = uso normale invariato).
|
|
4
|
+
* Blocca solo pattern chiaramente distruttivi o path sensibili; tutto il
|
|
5
|
+
* resto passa. Non sostituisce la revisione umana dei comandi rischiosi.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const TOOL_POLICY_REVISION = "2026-09-19.1";
|
|
9
|
+
|
|
10
|
+
export interface ToolBlock {
|
|
11
|
+
blocked: true;
|
|
12
|
+
ruleId: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ToolAllow {
|
|
17
|
+
blocked: false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ToolVerdict = ToolBlock | ToolAllow;
|
|
21
|
+
|
|
22
|
+
const BASH_RULES: Array<{ ruleId: string; pattern: RegExp; reason: string }> = [
|
|
23
|
+
{
|
|
24
|
+
ruleId: "bash-rm-root",
|
|
25
|
+
pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\s+(\/\*|\/)\s*(--no-preserve-root)?\s*([;&|]|$)/,
|
|
26
|
+
reason: "rm ricorsivo forzato su / o /*",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
ruleId: "bash-forkbomb",
|
|
30
|
+
pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/,
|
|
31
|
+
reason: "fork bomb",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
ruleId: "bash-mkfs",
|
|
35
|
+
pattern: /(^|[;&|]\s*|\bsudo\s+)mkfs(\s|[.]|$)/,
|
|
36
|
+
reason: "mkfs (formattazione filesystem)",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
ruleId: "bash-dd-device",
|
|
40
|
+
pattern: /\bdd\b[^;&|]*\bof=\/dev\//,
|
|
41
|
+
reason: "dd con output su device",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
ruleId: "bash-overwrite-disk",
|
|
45
|
+
pattern: />\s*\/dev\/(sd[a-z]+|hd[a-z]+|nvme\d+n\d+|vd[a-z]+|mmcblk\d+)/,
|
|
46
|
+
reason: "scrittura diretta su disco",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
ruleId: "bash-chmod-root",
|
|
50
|
+
pattern: /\bchmod\b[^;&|]*-R[^;&|]*\b777\b[^;&|]*\s\/(\s|[;&|]|$)/,
|
|
51
|
+
reason: "chmod -R 777 su /",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
ruleId: "bash-git-clean-root",
|
|
55
|
+
pattern: /\bgit\s+clean\b[^;&|]*\s\/(\s|[;&|]|$)/,
|
|
56
|
+
reason: "git clean su /",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const POWERSHELL_RULES: Array<{ ruleId: string; pattern: RegExp; reason: string }> = [
|
|
61
|
+
{
|
|
62
|
+
ruleId: "ps-format-volume",
|
|
63
|
+
pattern: /\bFormat-Volume\b/i,
|
|
64
|
+
reason: "Format-Volume",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
ruleId: "ps-format-drive",
|
|
68
|
+
pattern: /(^|[;&|]\s*)format\s+[a-z]:/i,
|
|
69
|
+
reason: "format su drive",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
ruleId: "ps-remove-root",
|
|
73
|
+
pattern: /\bRemove-Item\b[^;&|]*C:\\?(\s|[;&|]|$)[^;&|]*-Recurse/i,
|
|
74
|
+
reason: "Remove-Item ricorsivo su C:\\",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
function stripSudoPrefix(command: string): string {
|
|
79
|
+
return command.replace(/^\s*sudo\s+(-[^\s]+\s+)*/i, "");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function checkShellCommand(
|
|
83
|
+
command: unknown,
|
|
84
|
+
shell: "bash" | "powershell",
|
|
85
|
+
): ToolVerdict {
|
|
86
|
+
if (typeof command !== "string" || command.trim() === "") {
|
|
87
|
+
return { blocked: false };
|
|
88
|
+
}
|
|
89
|
+
const candidates = [command, stripSudoPrefix(command)];
|
|
90
|
+
const rules = shell === "bash" ? BASH_RULES : POWERSHELL_RULES;
|
|
91
|
+
for (const rule of rules) {
|
|
92
|
+
if (candidates.some((c) => rule.pattern.test(c))) {
|
|
93
|
+
return { blocked: true, ruleId: rule.ruleId, reason: rule.reason };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// powershell -Command "..." annidato in bash: controlla anche il contenuto quotato.
|
|
97
|
+
if (shell === "bash" && /powershell/i.test(command)) {
|
|
98
|
+
for (const rule of POWERSHELL_RULES) {
|
|
99
|
+
if (rule.pattern.test(command)) {
|
|
100
|
+
return { blocked: true, ruleId: rule.ruleId, reason: rule.reason };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { blocked: false };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function checkFilePath(path: unknown): ToolVerdict {
|
|
108
|
+
if (typeof path !== "string" || path.trim() === "") {
|
|
109
|
+
return { blocked: false };
|
|
110
|
+
}
|
|
111
|
+
const normalized = path.replace(/\\/g, "/");
|
|
112
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
113
|
+
const basename = segments[segments.length - 1] ?? "";
|
|
114
|
+
|
|
115
|
+
if (basename === ".env") {
|
|
116
|
+
return { blocked: true, ruleId: "path-dotenv", reason: "scrittura su file .env" };
|
|
117
|
+
}
|
|
118
|
+
if (segments.includes(".ssh")) {
|
|
119
|
+
return { blocked: true, ruleId: "path-ssh", reason: "scrittura sotto .ssh/" };
|
|
120
|
+
}
|
|
121
|
+
if (basename.toLowerCase().endsWith(".pem")) {
|
|
122
|
+
return { blocked: true, ruleId: "path-pem", reason: "scrittura su chiave .pem" };
|
|
123
|
+
}
|
|
124
|
+
if (segments.includes("node_modules")) {
|
|
125
|
+
return { blocked: true, ruleId: "path-node-modules", reason: "scrittura sotto node_modules/" };
|
|
126
|
+
}
|
|
127
|
+
return { blocked: false };
|
|
128
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import type { JevConfig } from "./config.ts";
|
|
4
|
+
import type { ReviewFn } from "./reviewer.ts";
|
|
5
|
+
import { reviewWithCoverage } from "./reviewer.ts";
|
|
6
|
+
import type { Metrics } from "./metrics.ts";
|
|
7
|
+
|
|
8
|
+
export function registerJevTool(
|
|
9
|
+
pi: ExtensionAPI,
|
|
10
|
+
deps: { review: () => ReviewFn; config: () => JevConfig; metrics: Metrics },
|
|
11
|
+
) {
|
|
12
|
+
pi.registerTool({
|
|
13
|
+
name: "jev_validate",
|
|
14
|
+
label: "Jev validation",
|
|
15
|
+
description:
|
|
16
|
+
"Check code or a decision against explicit requirements. " +
|
|
17
|
+
"Returns pass, block, review, or unavailable.",
|
|
18
|
+
promptSnippet: "Validate a specific code fragment or decision with Jev.",
|
|
19
|
+
promptGuidelines: [
|
|
20
|
+
"Use jev_validate when an independent targeted check is useful.",
|
|
21
|
+
"Do not describe review or unavailable as successful validation.",
|
|
22
|
+
"Jev validation does not replace compilation or tests.",
|
|
23
|
+
],
|
|
24
|
+
parameters: Type.Object({
|
|
25
|
+
requirements: Type.String({ minLength: 1, maxLength: 16000 }),
|
|
26
|
+
candidate: Type.String({ minLength: 1, maxLength: 100000 }),
|
|
27
|
+
}),
|
|
28
|
+
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
|
|
29
|
+
const cfg = deps.config();
|
|
30
|
+
const result = await reviewWithCoverage(
|
|
31
|
+
deps.review(),
|
|
32
|
+
{ requirements: params.requirements, candidate: params.candidate },
|
|
33
|
+
{ limits: cfg.limits, requirementsSensitive: true },
|
|
34
|
+
signal ?? undefined,
|
|
35
|
+
);
|
|
36
|
+
deps.metrics.record(result);
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
39
|
+
details: result,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|