@goodandready/dsh-approval-gate 0.1.15 → 0.1.16

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/lib/messages.js CHANGED
@@ -1,282 +1,294 @@
1
- import { redactCredentials } from "./redact.js";
2
- export const LOCALE_NS = "dsh-approval-gate";
3
-
4
- export const MESSAGES = Object.freeze({
5
- en: {
6
- blockedPrefix: "Blocked by dsh-approval-gate rule",
7
- detectedLabel: "Detected",
8
- commandLabel: "Command",
9
- parseReasonLabel: "Parse reason",
10
- hintPrefix: "Hint",
11
- detailProcessSubstitution: "process substitution needs confirmation",
12
- detailUnclosedProcessSubstitution: "unclosed process substitution",
13
- detailUnclosedCommandSubstitution: "unclosed command substitution",
14
- detailUnclosedBacktick: "unclosed backtick substitution",
15
- detailUnclosedParameterExpansion: "unclosed parameter expansion",
16
- detailUnclosedSingleQuote: "unclosed single quote",
17
- detailUnclosedDoubleQuote: "unclosed double quote",
18
- detailDanglingEscape: "unfinished escape",
19
- detailInvalidHeredoc: "invalid here-document delimiter",
20
- detailUnterminatedHeredoc: "here-document terminator was not found",
21
- detailUnsupportedGrouping: "shell grouping syntax is not supported by the analyzer",
22
- detailUnsupported: "unsupported shell syntax",
23
- approvalRequired: "Approval required",
24
- askUnknown: "The command could not be fully inspected; review it before allowing",
25
- askUnparseable: "Shell syntax could not be fully inspected; review it before allowing",
26
- askUnknownFormat: "The tool call format could not be inspected; review it before allowing",
27
- askUnknownWriteTarget: "The file write target could not be identified; review it before allowing",
28
- askDynamicCommand: "The expanded command name cannot be verified; review it before allowing",
29
- askDynamicArguments: "Expanded arguments to a sensitive command cannot be verified; review it before allowing",
30
- askDynamicRedirect: "The expanded redirect target cannot be verified; review it before allowing",
31
- askScriptFile: "The script file contents are not available to the guard; review it before allowing",
32
- askShellStdin: "Shell input is not fully available to the guard; review it before allowing",
33
- askInterpreterPayload: "Interpreter side effects are not fully analyzed; review it before allowing",
34
- askCredentialInArgv: "Expanded authorization values become process arguments; use a credential-safe API helper or review before allowing",
35
- askGitRemotePush: "Publishing commits to a remote repository via git push requires confirmation",
36
- askSilentErrorMasked: "The command suppresses errors or masks non-zero exit codes; review before allowing",
37
- ruleUnknown: "A blocking safety rule matched",
38
- ruleRecursiveRm: "Recursive file deletion",
39
- ruleProcessSignal: "Process termination",
40
- ruleServiceControl: "Service state change",
41
- ruleGitClean: "Destructive git clean",
42
- ruleGitResetHard: "Destructive git reset --hard",
43
- ruleGitForcePush: "Forceful git push",
44
- ruleGitBranchForceDelete: "Forceful git branch deletion",
45
- ruleGitCheckoutForce: "Forceful checkout discarding local modifications",
46
- ruleGitRestoreWorktree: "Restoring working tree files discarding modifications",
47
- ruleFindDelete: "find -delete",
48
- ruleDestructiveSql: "Destructive SQL",
49
- ruleSecretWrite: "Write to a protected secret file",
50
- ruleDeviceWrite: "Write to a device or protected file",
51
- ruleMkfsDevice: "Filesystem formatting",
52
- ruleCurlPipe: "Downloaded content piped to a shell",
53
- ruleEval: "Dynamic shell evaluation",
54
- ruleNestedShell: "Nested shell depth limit",
55
- ruleInterpreterPayload: "Unsafe interpreter payload",
56
- ruleEnvDumpLeak: "Process environment or credential exfiltration",
57
- rulePathTraversal: "Directory traversal attack detected",
58
- ruleBandHardDeny: "Command matched Band 0 critical security blocklist",
59
- ruleCircuitBreakerTripped: "Circuit breaker tripped due to repeated security denials",
60
- ruleSilentErrorMasked: "Suppression of command failure or error stream",
61
- ruleChmodChown: "Permission or ownership mutation",
62
- ruleReviewerReadOnly: "Reviewer agent is strictly isolated in read-only mode",
63
- ruleRetryExhaustion: "Exhausted repeated attempts to bypass safety gate",
64
- hintRecursiveRm: "Specify exact target files instead of recursive deletion",
65
- hintPathTraversal: "Keep operations strictly within the project directory",
66
- hintSecretWrite: "Modify configuration via settings or environment variables, not raw file overwrite",
67
- hintEnvDumpLeak: "Query specific non-secret environment variables individually",
68
- hintCircuitBreakerTripped: "Request manual operator approval before attempting new system commands",
69
- hintDestructiveSql: "Use migrations or execute non-destructive database operations",
70
- hintCurlPipe: "Download the script, inspect its content, and execute locally",
71
- hintGitForcePush: "Push branch without force or check with remote administrator",
72
- hintGitRemotePush: "Confirm remote target branch and repository before pushing",
73
- hintSilentErrorMasked: "Allow standard error output to be captured and handle non-zero exit codes explicitly",
74
- hintChmodChown: "Avoid modifying file permissions or system directory ownership directly",
75
- hintReviewerReadOnly: "Reviewer agents may only read repository files, not run commands or modify files",
76
- hintRetryExhaustion: "Repeated denials detected; manual operator intervention required",
77
- },
78
- zh: {
79
- blockedPrefix: "dsh-approval-gate 规则已阻止",
80
- detectedLabel: "检测到",
81
- commandLabel: "命令",
82
- parseReasonLabel: "解析原因",
83
- hintPrefix: "建议",
84
- detailProcessSubstitution: "进程替换需要确认",
85
- detailUnclosedProcessSubstitution: "进程替换未闭合",
86
- detailUnclosedCommandSubstitution: "命令替换未闭合",
87
- detailUnclosedBacktick: "反引号命令替换未闭合",
88
- detailUnclosedParameterExpansion: "参数展开未闭合",
89
- detailUnclosedSingleQuote: "单引号未闭合",
90
- detailUnclosedDoubleQuote: "双引号未闭合",
91
- detailDanglingEscape: "未完成的转义符",
92
- detailInvalidHeredoc: "无效的 heredoc 定界符",
93
- detailUnterminatedHeredoc: "未找到 heredoc 结束符",
94
- detailUnsupportedGrouping: "分析器不支持该 shell 分组语法",
95
- detailUnsupported: "不支持的 shell 语法",
96
- approvalRequired: "需要批准",
97
- askUnknown: "命令无法完整检查;请在允许前进行审查",
98
- askUnparseable: "Shell 语法无法完整检查;请在允许前进行审查",
99
- askUnknownFormat: "工具调用格式无法检查;请在允许前进行审查",
100
- askUnknownWriteTarget: "无法识别文件写入目标;请在允许前进行审查",
101
- askDynamicCommand: "无法验证展开后的命令名;请在允许前进行审查",
102
- askDynamicArguments: "敏感命令的展开参数无法验证;请在允许前进行审查",
103
- askDynamicRedirect: "展开后的重定向目标无法验证;请在允许前进行审查",
104
- askScriptFile: "脚本文件内容不可见;请在允许前进行审查",
105
- askShellStdin: "卫兵无法完整获取 Shell 输入;请在允许前进行审查",
106
- askInterpreterPayload: "解释器副作用未完全分析;请在允许前进行审查",
107
- askCredentialInArgv: "展开后的授权凭据作为进程参数传递;请使用凭据安全的 API 辅助工具,或在允许前进行审查",
108
- askGitRemotePush: "通过 git push 向远程仓库发布代码需要确认",
109
- askSilentErrorMasked: "命令屏蔽了错误输出或强制将退出码设为 0;请在允许前审查",
110
- ruleUnknown: "命中阻止性安全规则",
111
- ruleRecursiveRm: "递归删除文件",
112
- ruleProcessSignal: "终止进程",
113
- ruleServiceControl: "更改服务状态",
114
- ruleGitClean: "破坏性 git clean",
115
- ruleGitResetHard: "破坏性 git reset --hard",
116
- ruleGitForcePush: "强制 git push",
117
- ruleGitBranchForceDelete: "强制删除 git 分支",
118
- ruleGitCheckoutForce: "强制 checkout 丢弃本地修改",
119
- ruleGitRestoreWorktree: "还原工作区文件并丢弃修改",
120
- ruleFindDelete: "find -delete",
121
- ruleDestructiveSql: "破坏性 SQL",
122
- ruleSecretWrite: "写入受保护的敏感文件",
123
- ruleDeviceWrite: "写入系统设备或受保护文件",
124
- ruleMkfsDevice: "格式化文件系统",
125
- ruleCurlPipe: "将下载内容通过管道传入 shell",
126
- ruleEval: "动态 shell 执行 (eval)",
127
- ruleNestedShell: "超出嵌套 shell 深度限制",
128
- ruleInterpreterPayload: "不安全的解释器执行代码",
129
- ruleEnvDumpLeak: "尝试泄露环境变量与敏感凭据",
130
- rulePathTraversal: "检测到路径穿越攻击",
131
- ruleBandHardDeny: "命令匹配 Band 0 关键安全黑名单",
132
- ruleCircuitBreakerTripped: "因连续多次安全拒绝,断路熔断器已触发",
133
- ruleSilentErrorMasked: "屏蔽错误输出或强制忽略退出码",
134
- ruleChmodChown: "修改权限或所有权",
135
- ruleReviewerReadOnly: "审查者代理处于只读隔离模式",
136
- ruleRetryExhaustion: "多次尝试绕过安全网关已达上限",
137
- hintRecursiveRm: "请指定具体目标文件,避免递归删除",
138
- hintPathTraversal: "请将操作限制在项目根目录内",
139
- hintSecretWrite: "请通过设置或环境变量修改配置,不要直接覆盖敏感配置文件",
140
- hintEnvDumpLeak: "请单独查询非敏感环境变量,避免全量导出",
141
- hintCircuitBreakerTripped: "请在尝试新的系统命令前请求操作员人工授权",
142
- hintDestructiveSql: "请使用版本化数据库迁移,或执行非破坏性查询",
143
- hintCurlPipe: "先将脚本下载到本地检查内容,再安全执行",
144
- hintGitForcePush: "请勿使用 force 参数推送分支,或与远程仓库管理员确认",
145
- hintGitRemotePush: "推送前请核对目标分支与远程仓库地址",
146
- hintSilentErrorMasked: "请保留标准错误输出,并明确处理非零退出码",
147
- hintChmodChown: "避免直接修改系统目录所有权或文件权限",
148
- hintReviewerReadOnly: "审查者代理仅可读取项目文件,禁止执行命令或修改文件",
149
- hintRetryExhaustion: "检测到重复拒绝;需要操作员手动介入",
150
- },
151
- });
152
-
153
- export function sessionTag(execution) {
154
- const agent = execution && execution.agent;
155
- if (typeof agent === "string") return " (session " + agent + ")";
156
- if (agent && agent.session) {
157
- const s = agent.session;
158
- const sid = typeof s === "string" ? s : (s.id || s.name || s.sessionId || s.sessionKey);
159
- return sid ? " (session " + String(sid) + ")" : "";
160
- }
161
- return "";
162
- }
163
-
164
- function safeSnippet(value) {
165
- const flattened = String(value || "").replace(/[\r\n\t\u0000-\u001f]+/g, " ");
166
- return redactCredentials(flattened).slice(0, 120);
167
- }
168
-
169
- function translated(translate, key, logger) {
170
- if (typeof translate === "function") {
171
- try {
172
- const value = translate(key);
173
- if (value && value !== key) return value;
174
- } catch {
175
- if (logger && typeof logger.warn === "function") {
176
- logger.warn("[dsh-approval-gate] localization lookup failed; using English fallback");
177
- }
178
- }
179
- }
180
- return MESSAGES.en[key] || key;
181
- }
182
-
183
- const RULE_KEYS = Object.freeze({
184
- "recursive-rm": "ruleRecursiveRm",
185
- "process-signal": "ruleProcessSignal",
186
- "service-control": "ruleServiceControl",
187
- "git-clean": "ruleGitClean",
188
- "git-reset-hard": "ruleGitResetHard",
189
- "git-force-push": "ruleGitForcePush",
190
- "git-branch-force-delete": "ruleGitBranchForceDelete",
191
- "git-checkout-force": "ruleGitCheckoutForce",
192
- "git-restore-worktree": "ruleGitRestoreWorktree",
193
- "find-delete": "ruleFindDelete",
194
- "destructive-sql": "ruleDestructiveSql",
195
- "secret-write": "ruleSecretWrite",
196
- "device-write": "ruleDeviceWrite",
197
- "mkfs-device": "ruleMkfsDevice",
198
- "curl-pipe": "ruleCurlPipe",
199
- "eval": "ruleEval",
200
- "nested-shell": "ruleNestedShell",
201
- "interpreter-payload": "ruleInterpreterPayload",
202
- "env-dump-leak": "ruleEnvDumpLeak",
203
- "path-traversal": "rulePathTraversal",
204
- "band-hard-deny": "ruleBandHardDeny",
205
- "circuit-breaker-tripped": "ruleCircuitBreakerTripped",
206
- "silent-error-masked": "ruleSilentErrorMasked",
207
- "chmod-chown": "ruleChmodChown",
208
- "reviewer-read-only": "ruleReviewerReadOnly",
209
- "retry-exhaustion": "ruleRetryExhaustion",
210
- });
211
-
212
- const HINT_KEYS = Object.freeze({
213
- "recursive-rm": "hintRecursiveRm",
214
- "path-traversal": "hintPathTraversal",
215
- "secret-write": "hintSecretWrite",
216
- "env-dump-leak": "hintEnvDumpLeak",
217
- "circuit-breaker-tripped": "hintCircuitBreakerTripped",
218
- "destructive-sql": "hintDestructiveSql",
219
- "curl-pipe": "hintCurlPipe",
220
- "git-force-push": "hintGitForcePush",
221
- "git-remote-push": "hintGitRemotePush",
222
- "silent-error-masked": "hintSilentErrorMasked",
223
- "chmod-chown": "hintChmodChown",
224
- "reviewer-read-only": "hintReviewerReadOnly",
225
- "retry-exhaustion": "hintRetryExhaustion",
226
- });
227
-
228
- const ASK_KEYS = Object.freeze({
229
- unparseable: "askUnparseable",
230
- "unknown-format": "askUnknownFormat",
231
- "unknown-write-target": "askUnknownWriteTarget",
232
- "dynamic-command": "askDynamicCommand",
233
- "dynamic-arguments": "askDynamicArguments",
234
- "dynamic-redirect": "askDynamicRedirect",
235
- "script-file": "askScriptFile",
236
- "shell-stdin": "askShellStdin",
237
- "interpreter-payload": "askInterpreterPayload",
238
- "credential-in-argv": "askCredentialInArgv",
239
- "git-remote-push": "askGitRemotePush",
240
- "silent-error-masked": "askSilentErrorMasked",
241
- });
242
-
243
- const DETAIL_KEYS = Object.freeze({
244
- "process-substitution": "detailProcessSubstitution",
245
- "unclosed-process-substitution": "detailUnclosedProcessSubstitution",
246
- "unclosed-command-substitution": "detailUnclosedCommandSubstitution",
247
- "unclosed-backtick-substitution": "detailUnclosedBacktick",
248
- "unclosed-parameter-expansion": "detailUnclosedParameterExpansion",
249
- "unclosed-single-quote": "detailUnclosedSingleQuote",
250
- "unclosed-double-quote": "detailUnclosedDoubleQuote",
251
- "dangling-backslash": "detailDanglingEscape",
252
- "invalid-heredoc-delimiter": "detailInvalidHeredoc",
253
- "unterminated-heredoc": "detailUnterminatedHeredoc",
254
- "unsupported-shell-grouping": "detailUnsupportedGrouping",
255
- });
256
-
257
- export function denyMessage(hit, who, translate, logger) {
258
- const reason = hit && hit.reason;
259
- const key = RULE_KEYS[reason] || "ruleUnknown";
260
- const snippet = safeSnippet(hit && hit.snippet);
261
- const parts = [translated(translate, "blockedPrefix", logger), translated(translate, key, logger)];
262
- if (snippet) parts.push(translated(translate, "detectedLabel", logger) + ": " + snippet);
263
- const hintKey = HINT_KEYS[reason];
264
- if (hintKey) {
265
- parts.push(translated(translate, "hintPrefix", logger) + ": " + translated(translate, hintKey, logger));
266
- }
267
- if (who) parts.push(who.trim());
268
- return parts.join(" — ") + ".";
269
- }
270
-
271
- export function askMessage(hit, who, translate, logger) {
272
- const key = ASK_KEYS[hit && hit.reason] || "askUnknown";
273
- const snippet = safeSnippet(hit && hit.snippet);
274
- const parts = [translated(translate, "approvalRequired", logger), translated(translate, key, logger)];
275
- if (hit && hit.reason === "unparseable") {
276
- const detailKey = DETAIL_KEYS[hit.detail] || "detailUnsupported";
277
- parts.push(translated(translate, "parseReasonLabel", logger) + ": " + translated(translate, detailKey, logger));
278
- }
279
- if (snippet) parts.push(translated(translate, "commandLabel", logger) + ": " + snippet);
280
- if (who) parts.push(who.trim());
281
- return parts.join(" — ") + ".";
282
- }
1
+ import { redactCredentials } from "./redact.js";
2
+ export const LOCALE_NS = "dsh-approval-gate";
3
+
4
+ export const MESSAGES = Object.freeze({
5
+ en: {
6
+ blockedPrefix: "Blocked by dsh-approval-gate rule",
7
+ detectedLabel: "Detected",
8
+ commandLabel: "Command",
9
+ parseReasonLabel: "Parse reason",
10
+ hintPrefix: "Hint",
11
+ detailProcessSubstitution: "process substitution needs confirmation",
12
+ detailUnclosedProcessSubstitution: "unclosed process substitution",
13
+ detailUnclosedCommandSubstitution: "unclosed command substitution",
14
+ detailUnclosedBacktick: "unclosed backtick substitution",
15
+ detailUnclosedParameterExpansion: "unclosed parameter expansion",
16
+ detailUnclosedSingleQuote: "unclosed single quote",
17
+ detailUnclosedDoubleQuote: "unclosed double quote",
18
+ detailDanglingEscape: "unfinished escape",
19
+ detailInvalidHeredoc: "invalid here-document delimiter",
20
+ detailUnterminatedHeredoc: "here-document terminator was not found",
21
+ detailUnsupportedGrouping: "shell grouping syntax is not supported by the analyzer",
22
+ detailUnsupported: "unsupported shell syntax",
23
+ approvalRequired: "Approval required",
24
+ askUnknown: "The command could not be fully inspected; review it before allowing",
25
+ askUnparseable: "Shell syntax could not be fully inspected; review it before allowing",
26
+ askUnknownFormat: "The tool call format could not be inspected; review it before allowing",
27
+ askUnknownWriteTarget: "The file write target could not be identified; review it before allowing",
28
+ askDynamicCommand: "The expanded command name cannot be verified; review it before allowing",
29
+ askDynamicArguments: "Expanded arguments to a sensitive command cannot be verified; review it before allowing",
30
+ askDynamicRedirect: "The expanded redirect target cannot be verified; review it before allowing",
31
+ askScriptFile: "The script file contents are not available to the guard; review it before allowing",
32
+ askShellStdin: "Shell input is not fully available to the guard; review it before allowing",
33
+ askInterpreterPayload: "Interpreter side effects are not fully analyzed; review it before allowing",
34
+ askCredentialInArgv: "Expanded authorization values become process arguments; use a credential-safe API helper or review before allowing",
35
+ askGitRemotePush: "Publishing commits to a remote repository via git push requires confirmation",
36
+ askSilentErrorMasked: "The command suppresses errors or masks non-zero exit codes; review before allowing",
37
+ ruleUnknown: "A blocking safety rule matched",
38
+ ruleRecursiveRm: "Recursive file deletion",
39
+ ruleProcessSignal: "Process termination",
40
+ ruleServiceControl: "Service state change",
41
+ ruleGitClean: "Destructive git clean",
42
+ ruleGitResetHard: "Destructive git reset --hard",
43
+ ruleGitForcePush: "Forceful git push",
44
+ ruleGitBranchForceDelete: "Forceful git branch deletion",
45
+ ruleGitCheckoutForce: "Forceful checkout discarding local modifications",
46
+ ruleGitRestoreWorktree: "Restoring working tree files discarding modifications",
47
+ ruleFindDelete: "find -delete",
48
+ ruleDestructiveSql: "Destructive SQL",
49
+ ruleSecretWrite: "Write to a protected secret file",
50
+ ruleDeviceWrite: "Write to a device or protected file",
51
+ ruleMkfsDevice: "Filesystem formatting",
52
+ ruleCurlPipe: "Downloaded content piped to a shell",
53
+ ruleEval: "Dynamic shell evaluation",
54
+ ruleNestedShell: "Nested shell depth limit",
55
+ ruleInterpreterPayload: "Unsafe interpreter payload",
56
+ ruleEnvDumpLeak: "Process environment or credential exfiltration",
57
+ rulePathTraversal: "Directory traversal attack detected",
58
+ ruleBandHardDeny: "Command matched Band 0 critical security blocklist",
59
+ ruleCircuitBreakerTripped: "Circuit breaker tripped due to repeated security denials",
60
+ ruleSilentErrorMasked: "Suppression of command failure or error stream",
61
+ ruleChmodChown: "Permission or ownership mutation",
62
+ ruleReviewerReadOnly: "Reviewer agent is strictly isolated in read-only mode",
63
+ ruleRetryExhaustion: "Exhausted repeated attempts to bypass safety gate",
64
+ ruleFailClosedAudit: "Audit log write failure blocked by fail-closed policy",
65
+ ruleLockedAskUnattended: "Critical operation requires human confirmation and cannot be bypassed in unattended mode",
66
+ hintRecursiveRm: "Specify exact target files instead of recursive deletion",
67
+ hintPathTraversal: "Keep operations strictly within the project directory",
68
+ hintSecretWrite: "Modify configuration via settings or environment variables, not raw file overwrite",
69
+ hintEnvDumpLeak: "Query specific non-secret environment variables individually",
70
+ hintCircuitBreakerTripped: "Request manual operator approval before attempting new system commands",
71
+ hintDestructiveSql: "Use migrations or execute non-destructive database operations",
72
+ hintCurlPipe: "Download the script, inspect its content, and execute locally",
73
+ hintGitForcePush: "Push branch without force or check with remote administrator",
74
+ hintGitRemotePush: "Confirm remote target branch and repository before pushing",
75
+ hintSilentErrorMasked: "Allow standard error output to be captured and handle non-zero exit codes explicitly",
76
+ hintChmodChown: "Avoid modifying file permissions or system directory ownership directly",
77
+ hintReviewerReadOnly: "Reviewer agents may only read repository files, not run commands or modify files",
78
+ hintRetryExhaustion: "Repeated denials detected; manual operator intervention required",
79
+ hintFailClosedAudit: "Ensure the audit log directory is writable and disk space is available",
80
+ hintLockedAskUnattended: "Switch to an interactive session or request explicit human confirmation",
81
+ },
82
+ zh: {
83
+ blockedPrefix: "dsh-approval-gate 规则已阻止",
84
+ detectedLabel: "检测到",
85
+ commandLabel: "命令",
86
+ parseReasonLabel: "解析原因",
87
+ hintPrefix: "建议",
88
+ detailProcessSubstitution: "进程替换需要确认",
89
+ detailUnclosedProcessSubstitution: "进程替换未闭合",
90
+ detailUnclosedCommandSubstitution: "命令替换未闭合",
91
+ detailUnclosedBacktick: "反引号命令替换未闭合",
92
+ detailUnclosedParameterExpansion: "参数展开未闭合",
93
+ detailUnclosedSingleQuote: "单引号未闭合",
94
+ detailUnclosedDoubleQuote: "双引号未闭合",
95
+ detailDanglingEscape: "未完成的转义符",
96
+ detailInvalidHeredoc: "无效的 heredoc 定界符",
97
+ detailUnterminatedHeredoc: "未找到 heredoc 结束符",
98
+ detailUnsupportedGrouping: "分析器不支持该 shell 分组语法",
99
+ detailUnsupported: "不支持的 shell 语法",
100
+ approvalRequired: "需要批准",
101
+ askUnknown: "命令无法完整检查;请在允许前进行审查",
102
+ askUnparseable: "Shell 语法无法完整检查;请在允许前进行审查",
103
+ askUnknownFormat: "工具调用格式无法检查;请在允许前进行审查",
104
+ askUnknownWriteTarget: "无法识别文件写入目标;请在允许前进行审查",
105
+ askDynamicCommand: "无法验证展开后的命令名;请在允许前进行审查",
106
+ askDynamicArguments: "敏感命令的展开参数无法验证;请在允许前进行审查",
107
+ askDynamicRedirect: "展开后的重定向目标无法验证;请在允许前进行审查",
108
+ askScriptFile: "脚本文件内容不可见;请在允许前进行审查",
109
+ askShellStdin: "卫兵无法完整获取 Shell 输入;请在允许前进行审查",
110
+ askInterpreterPayload: "解释器副作用未完全分析;请在允许前进行审查",
111
+ askCredentialInArgv: "展开后的授权凭据作为进程参数传递;请使用凭据安全的 API 辅助工具,或在允许前进行审查",
112
+ askGitRemotePush: "通过 git push 向远程仓库发布代码需要确认",
113
+ askSilentErrorMasked: "命令屏蔽了错误输出或强制将退出码设为 0;请在允许前审查",
114
+ ruleUnknown: "命中阻止性安全规则",
115
+ ruleRecursiveRm: "递归删除文件",
116
+ ruleProcessSignal: "终止进程",
117
+ ruleServiceControl: "更改服务状态",
118
+ ruleGitClean: "破坏性 git clean",
119
+ ruleGitResetHard: "破坏性 git reset --hard",
120
+ ruleGitForcePush: "强制 git push",
121
+ ruleGitBranchForceDelete: "强制删除 git 分支",
122
+ ruleGitCheckoutForce: "强制 checkout 丢弃本地修改",
123
+ ruleGitRestoreWorktree: "还原工作区文件并丢弃修改",
124
+ ruleFindDelete: "find -delete",
125
+ ruleDestructiveSql: "破坏性 SQL",
126
+ ruleSecretWrite: "写入受保护的敏感文件",
127
+ ruleDeviceWrite: "写入系统设备或受保护文件",
128
+ ruleMkfsDevice: "格式化文件系统",
129
+ ruleCurlPipe: "将下载内容通过管道传入 shell",
130
+ ruleEval: "动态 shell 执行 (eval)",
131
+ ruleNestedShell: "超出嵌套 shell 深度限制",
132
+ ruleInterpreterPayload: "不安全的解释器执行代码",
133
+ ruleEnvDumpLeak: "尝试泄露环境变量与敏感凭据",
134
+ rulePathTraversal: "检测到路径穿越攻击",
135
+ ruleBandHardDeny: "命令匹配 Band 0 关键安全黑名单",
136
+ ruleCircuitBreakerTripped: "因连续多次安全拒绝,断路熔断器已触发",
137
+ ruleSilentErrorMasked: "屏蔽错误输出或强制忽略退出码",
138
+ ruleChmodChown: "修改权限或所有权",
139
+ ruleReviewerReadOnly: "审查者代理处于只读隔离模式",
140
+ ruleRetryExhaustion: "多次尝试绕过安全网关已达上限",
141
+ ruleFailClosedAudit: "审计日志写入失败,受安全失败策略阻止",
142
+ ruleLockedAskUnattended: "关键操作需要人工确认,无法在无人值守模式下自动批准",
143
+ hintRecursiveRm: "请指定具体目标文件,避免递归删除",
144
+ hintPathTraversal: "请将操作限制在项目根目录内",
145
+ hintSecretWrite: "请通过设置或环境变量修改配置,不要直接覆盖敏感配置文件",
146
+ hintEnvDumpLeak: "请单独查询非敏感环境变量,避免全量导出",
147
+ hintCircuitBreakerTripped: "请在尝试新的系统命令前请求操作员人工授权",
148
+ hintDestructiveSql: "请使用版本化数据库迁移,或执行非破坏性查询",
149
+ hintCurlPipe: "先将脚本下载到本地检查内容,再安全执行",
150
+ hintGitForcePush: "请勿使用 force 参数推送分支,或与远程仓库管理员确认",
151
+ hintGitRemotePush: "推送前请核对目标分支与远程仓库地址",
152
+ hintSilentErrorMasked: "请保留标准错误输出,并明确处理非零退出码",
153
+ hintChmodChown: "避免直接修改系统目录所有权或文件权限",
154
+ hintReviewerReadOnly: "审查者代理仅可读取项目文件,禁止执行命令或修改文件",
155
+ hintRetryExhaustion: "检测到重复拒绝;需要操作员手动介入",
156
+ hintFailClosedAudit: "请确保审计日志目录可写且磁盘空间充足",
157
+ hintLockedAskUnattended: "请切换到交互式会话或请求人工显式确认",
158
+ },
159
+ });
160
+
161
+ export function sessionTag(execution) {
162
+ const agent = execution && execution.agent;
163
+ if (typeof agent === "string") return " (session " + agent + ")";
164
+ if (agent && agent.session) {
165
+ const s = agent.session;
166
+ const sid = typeof s === "string" ? s : (s.id || s.name || s.sessionId || s.sessionKey);
167
+ return sid ? " (session " + String(sid) + ")" : "";
168
+ }
169
+ return "";
170
+ }
171
+
172
+ function safeSnippet(value) {
173
+ const flattened = String(value || "").replace(/[\r\n\t\u0000-\u001f]+/g, " ");
174
+ return redactCredentials(flattened).slice(0, 120);
175
+ }
176
+
177
+ function translated(translate, key, logger) {
178
+ if (typeof translate === "function") {
179
+ try {
180
+ const value = translate(key);
181
+ if (value && value !== key) return value;
182
+ } catch {
183
+ if (logger && typeof logger.warn === "function") {
184
+ logger.warn("[dsh-approval-gate] localization lookup failed; using English fallback");
185
+ }
186
+ }
187
+ }
188
+ return MESSAGES.en[key] || key;
189
+ }
190
+
191
+ const RULE_KEYS = Object.freeze({
192
+ "recursive-rm": "ruleRecursiveRm",
193
+ "process-signal": "ruleProcessSignal",
194
+ "service-control": "ruleServiceControl",
195
+ "git-clean": "ruleGitClean",
196
+ "git-reset-hard": "ruleGitResetHard",
197
+ "git-force-push": "ruleGitForcePush",
198
+ "git-branch-force-delete": "ruleGitBranchForceDelete",
199
+ "git-checkout-force": "ruleGitCheckoutForce",
200
+ "git-restore-worktree": "ruleGitRestoreWorktree",
201
+ "find-delete": "ruleFindDelete",
202
+ "destructive-sql": "ruleDestructiveSql",
203
+ "secret-write": "ruleSecretWrite",
204
+ "device-write": "ruleDeviceWrite",
205
+ "mkfs-device": "ruleMkfsDevice",
206
+ "curl-pipe": "ruleCurlPipe",
207
+ "eval": "ruleEval",
208
+ "nested-shell": "ruleNestedShell",
209
+ "interpreter-payload": "ruleInterpreterPayload",
210
+ "env-dump-leak": "ruleEnvDumpLeak",
211
+ "path-traversal": "rulePathTraversal",
212
+ "band-hard-deny": "ruleBandHardDeny",
213
+ "circuit-breaker-tripped": "ruleCircuitBreakerTripped",
214
+ "silent-error-masked": "ruleSilentErrorMasked",
215
+ "chmod-chown": "ruleChmodChown",
216
+ "reviewer-read-only": "ruleReviewerReadOnly",
217
+ "retry-exhaustion": "ruleRetryExhaustion",
218
+ "fail-closed-audit": "ruleFailClosedAudit",
219
+ "locked-ask-unattended-denied": "ruleLockedAskUnattended",
220
+ });
221
+
222
+ const HINT_KEYS = Object.freeze({
223
+ "recursive-rm": "hintRecursiveRm",
224
+ "path-traversal": "hintPathTraversal",
225
+ "secret-write": "hintSecretWrite",
226
+ "env-dump-leak": "hintEnvDumpLeak",
227
+ "circuit-breaker-tripped": "hintCircuitBreakerTripped",
228
+ "destructive-sql": "hintDestructiveSql",
229
+ "curl-pipe": "hintCurlPipe",
230
+ "git-force-push": "hintGitForcePush",
231
+ "git-remote-push": "hintGitRemotePush",
232
+ "silent-error-masked": "hintSilentErrorMasked",
233
+ "chmod-chown": "hintChmodChown",
234
+ "reviewer-read-only": "hintReviewerReadOnly",
235
+ "retry-exhaustion": "hintRetryExhaustion",
236
+ "fail-closed-audit": "hintFailClosedAudit",
237
+ "locked-ask-unattended-denied": "hintLockedAskUnattended",
238
+ });
239
+
240
+ const ASK_KEYS = Object.freeze({
241
+ unparseable: "askUnparseable",
242
+ "unknown-format": "askUnknownFormat",
243
+ "unknown-write-target": "askUnknownWriteTarget",
244
+ "dynamic-command": "askDynamicCommand",
245
+ "dynamic-arguments": "askDynamicArguments",
246
+ "dynamic-redirect": "askDynamicRedirect",
247
+ "script-file": "askScriptFile",
248
+ "shell-stdin": "askShellStdin",
249
+ "interpreter-payload": "askInterpreterPayload",
250
+ "credential-in-argv": "askCredentialInArgv",
251
+ "git-remote-push": "askGitRemotePush",
252
+ "silent-error-masked": "askSilentErrorMasked",
253
+ });
254
+
255
+ const DETAIL_KEYS = Object.freeze({
256
+ "process-substitution": "detailProcessSubstitution",
257
+ "unclosed-process-substitution": "detailUnclosedProcessSubstitution",
258
+ "unclosed-command-substitution": "detailUnclosedCommandSubstitution",
259
+ "unclosed-backtick-substitution": "detailUnclosedBacktick",
260
+ "unclosed-parameter-expansion": "detailUnclosedParameterExpansion",
261
+ "unclosed-single-quote": "detailUnclosedSingleQuote",
262
+ "unclosed-double-quote": "detailUnclosedDoubleQuote",
263
+ "dangling-backslash": "detailDanglingEscape",
264
+ "invalid-heredoc-delimiter": "detailInvalidHeredoc",
265
+ "unterminated-heredoc": "detailUnterminatedHeredoc",
266
+ "unsupported-shell-grouping": "detailUnsupportedGrouping",
267
+ });
268
+
269
+ export function denyMessage(hit, who, translate, logger) {
270
+ const reason = hit && hit.reason;
271
+ const key = RULE_KEYS[reason] || "ruleUnknown";
272
+ const snippet = safeSnippet(hit && hit.snippet);
273
+ const parts = [translated(translate, "blockedPrefix", logger), translated(translate, key, logger)];
274
+ if (snippet) parts.push(translated(translate, "detectedLabel", logger) + ": " + snippet);
275
+ const hintKey = HINT_KEYS[reason];
276
+ if (hintKey) {
277
+ parts.push(translated(translate, "hintPrefix", logger) + ": " + translated(translate, hintKey, logger));
278
+ }
279
+ if (who) parts.push(who.trim());
280
+ return parts.join(" — ") + ".";
281
+ }
282
+
283
+ export function askMessage(hit, who, translate, logger) {
284
+ const key = ASK_KEYS[hit && hit.reason] || "askUnknown";
285
+ const snippet = safeSnippet(hit && hit.snippet);
286
+ const parts = [translated(translate, "approvalRequired", logger), translated(translate, key, logger)];
287
+ if (hit && hit.reason === "unparseable") {
288
+ const detailKey = DETAIL_KEYS[hit.detail] || "detailUnsupported";
289
+ parts.push(translated(translate, "parseReasonLabel", logger) + ": " + translated(translate, detailKey, logger));
290
+ }
291
+ if (snippet) parts.push(translated(translate, "commandLabel", logger) + ": " + snippet);
292
+ if (who) parts.push(who.trim());
293
+ return parts.join(" — ") + ".";
294
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-approval-gate",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Host-only command safety gate for DeepSeek Harness: blocks recognized dangerous operations and requests DSH approval when a command cannot be inspected.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",