@trim21/personal-pi-extensions 0.1.699 → 0.1.20261007141935
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/package.json +1 -1
- package/src/bwrap/html.ts +1 -1
- package/src/bwrap/runtime.ts +2 -13
- package/src/bwrap/dcg-scan.ts +0 -131
package/package.json
CHANGED
package/src/bwrap/html.ts
CHANGED
package/src/bwrap/runtime.ts
CHANGED
|
@@ -42,7 +42,6 @@ import {
|
|
|
42
42
|
resolveBwrapPath,
|
|
43
43
|
type ResolvedBwrap,
|
|
44
44
|
} from "./core.js";
|
|
45
|
-
import { dcgSuggestion } from "./dcg-scan.js";
|
|
46
45
|
import { escapeHtml } from "./html.js";
|
|
47
46
|
import { loadSandboxConfig, runInSandbox } from "./sandbox.js";
|
|
48
47
|
|
|
@@ -774,25 +773,15 @@ export class BwrapRuntime {
|
|
|
774
773
|
// 子菜单只列出未命中 allow 规则的 pattern:已提前允许的部分自动放行,
|
|
775
774
|
// 无需再展示或重复勾选持久化(deny 命中的命令在 evaluate 阶段已被拒绝)。
|
|
776
775
|
const unallowedPatterns = await ruleSet.pendingPatterns(command);
|
|
777
|
-
//
|
|
778
|
-
// 时 notify 提示,弹窗本身与无 dcg 时一致
|
|
779
|
-
const outcome = await dcgSuggestion(command);
|
|
780
|
-
if (outcome.kind === "failed") {
|
|
781
|
-
ctx.ui.notify(`dcg 扫描失败,本次无破坏性命令建议: ${outcome.detail}`, "warning");
|
|
782
|
-
}
|
|
783
|
-
// 弹框主体按行组织('\n' join),便于 review;suggestion 块带前导空行 +
|
|
784
|
-
// 尾部 "---" 分隔。
|
|
776
|
+
// 弹框主体按行组织('\n' join),便于 review。
|
|
785
777
|
const lines: string[] = [
|
|
786
778
|
"Allow this command to run without sandbox?",
|
|
787
779
|
"---",
|
|
788
780
|
"",
|
|
789
781
|
`Reason: ${escapeHtml(reason ?? "(No reason provided by model)")}`,
|
|
790
782
|
"---",
|
|
783
|
+
fenceCodeBlock(command),
|
|
791
784
|
];
|
|
792
|
-
if (outcome.kind === "suggestion") {
|
|
793
|
-
lines.push("", outcome.suggestion.text, "---");
|
|
794
|
-
}
|
|
795
|
-
lines.push(fenceCodeBlock(command));
|
|
796
785
|
// 执行目录与工作区不同时,提示实际执行目录(execCwd 是解析后的绝对路径,
|
|
797
786
|
// 显示用 pretty path 风格:home 内 `~/…`,否则绝对路径)
|
|
798
787
|
if (execCwd !== ctx.cwd) {
|
package/src/bwrap/dcg-scan.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dcg-scan —— 可选的 dcg(Destructive Command Guard)扫描建议层。
|
|
3
|
-
*
|
|
4
|
-
* 系统安装了 dcg 时,在 full-access 人工审批弹窗里附加一段破坏性命令
|
|
5
|
-
* 扫描建议,作为人工 review 的参考;dcg 未安装、调用失败或超时时静默
|
|
6
|
-
* 跳过,不影响审批流程。dcg 输出只作建议文本,不参与任何执行决策。
|
|
7
|
-
*/
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
|
-
|
|
10
|
-
import { escapeHtml } from "./html.js";
|
|
11
|
-
|
|
12
|
-
/** `dcg test --format json` 输出中我们关心的字段。 */
|
|
13
|
-
interface DcgTestOutput {
|
|
14
|
-
decision?: "allow" | "deny" | "indeterminate";
|
|
15
|
-
severity?: "critical" | "high" | "medium" | "low";
|
|
16
|
-
rule_id?: string;
|
|
17
|
-
reason?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** dcg 扫描建议(纯文本,外部字段已转义,可直接拼进弹窗 description)。 */
|
|
21
|
-
export interface DcgSuggestion {
|
|
22
|
-
kind: "danger" | "clean";
|
|
23
|
-
text: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** dcg 扫描结果:正常判定 / 未安装(静默跳过)/ 扫描失败(应提示用户)。 */
|
|
27
|
-
export type DcgScanOutcome =
|
|
28
|
-
| { kind: "suggestion"; suggestion: DcgSuggestion }
|
|
29
|
-
| { kind: "not-installed" }
|
|
30
|
-
| { kind: "failed"; detail: string };
|
|
31
|
-
|
|
32
|
-
/** dcg 扫描的超时预算:超时视为无建议,不让审批弹窗被拖住。 */
|
|
33
|
-
const DCG_SCAN_TIMEOUT_MS = 2000;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 对命令做 dcg 扫描。返回三种结果:
|
|
37
|
-
* - `suggestion`:正常判定,携带建议文本
|
|
38
|
-
* - `not-installed`:dcg 未安装(调用方静默跳过)
|
|
39
|
-
* - `failed`:dcg 已安装但扫描失败(退出码异常/超时/输出不可解析),
|
|
40
|
-
* 调用方应通过 `ui.notify` 提示用户
|
|
41
|
-
*/
|
|
42
|
-
export async function dcgSuggestion(command: string): Promise<DcgScanOutcome> {
|
|
43
|
-
let stdout: string;
|
|
44
|
-
try {
|
|
45
|
-
stdout = await runDcgScan(command);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
48
|
-
return { kind: "not-installed" };
|
|
49
|
-
}
|
|
50
|
-
return { kind: "failed", detail: error instanceof Error ? error.message : String(error) };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let output: DcgTestOutput;
|
|
54
|
-
try {
|
|
55
|
-
output = JSON.parse(stdout) as DcgTestOutput;
|
|
56
|
-
} catch {
|
|
57
|
-
return { kind: "failed", detail: "无法解析 dcg 输出" };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (output.decision === "deny") {
|
|
61
|
-
const details = [
|
|
62
|
-
output.rule_id ? `rule: ${escapeHtml(output.rule_id)}` : undefined,
|
|
63
|
-
output.severity ? `severity: ${escapeHtml(output.severity)}` : undefined,
|
|
64
|
-
]
|
|
65
|
-
.filter(Boolean)
|
|
66
|
-
.join(", ");
|
|
67
|
-
const reason = output.reason ? escapeHtml(output.reason) : "检测到破坏性命令模式";
|
|
68
|
-
return {
|
|
69
|
-
kind: "suggestion",
|
|
70
|
-
suggestion: {
|
|
71
|
-
kind: "danger",
|
|
72
|
-
text: `dcg 建议拦截: ${reason}${details ? ` (${details})` : ""}`,
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
if (output.decision === "allow") {
|
|
77
|
-
return {
|
|
78
|
-
kind: "suggestion",
|
|
79
|
-
suggestion: { kind: "clean", text: "dcg 未检测到破坏性命令模式" },
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
// indeterminate / 缺字段:dcg 未能给出可用判定
|
|
83
|
-
return {
|
|
84
|
-
kind: "failed",
|
|
85
|
-
detail: `dcg 未返回可用判定 (decision=${output.decision ?? "missing"})`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function runDcgScan(command: string): Promise<string> {
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
const proc = spawn("dcg", ["test", "--stdin", "--format", "json"], {
|
|
92
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
93
|
-
});
|
|
94
|
-
let stdout = "";
|
|
95
|
-
let settled = false;
|
|
96
|
-
const timeoutId = setTimeout(() => {
|
|
97
|
-
proc.kill("SIGKILL");
|
|
98
|
-
settle(new Error("dcg scan timed out"));
|
|
99
|
-
}, DCG_SCAN_TIMEOUT_MS);
|
|
100
|
-
const settle = (error?: Error) => {
|
|
101
|
-
if (settled) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
settled = true;
|
|
105
|
-
clearTimeout(timeoutId);
|
|
106
|
-
if (error) {
|
|
107
|
-
reject(error);
|
|
108
|
-
} else {
|
|
109
|
-
resolve(stdout);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
proc.stdout.setEncoding("utf8");
|
|
113
|
-
proc.stdout.on("data", (chunk: string) => {
|
|
114
|
-
stdout += chunk;
|
|
115
|
-
});
|
|
116
|
-
proc.on("error", (error: NodeJS.ErrnoException) => {
|
|
117
|
-
// ENOENT = dcg 未安装:静默跳过
|
|
118
|
-
settle(error);
|
|
119
|
-
});
|
|
120
|
-
proc.on("close", (code) => {
|
|
121
|
-
// dcg 的退出码是决策结果(deny 时非 0),不是失败标志:只要 stdout
|
|
122
|
-
// 有内容就交给上层解析;真正出错时(参数错误等)stdout 为空。
|
|
123
|
-
if (code === 0 || stdout.length > 0) {
|
|
124
|
-
settle();
|
|
125
|
-
} else {
|
|
126
|
-
settle(new Error(`dcg exited with code ${String(code)}`));
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
proc.stdin.end(command);
|
|
130
|
-
});
|
|
131
|
-
}
|