@goodandready/dsh-approval-gate 0.1.16 → 0.1.17
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/CHANGELOG.md +154 -146
- package/lib/classifier.js +392 -0
- package/lib/engine.js +464 -407
- package/lib/index.js +36 -0
- package/lib/messages.js +18 -0
- package/package.json +55 -55
package/lib/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { SecurityEngine } from "./engine.js";
|
|
|
2
2
|
import { BandsEngine } from "./bands.js";
|
|
3
3
|
import { AuditLogger } from "./audit-log.js";
|
|
4
4
|
import { DeclarativeRulesEngine } from "./rules-loader.js";
|
|
5
|
+
import { LlmClassifier } from "./classifier.js";
|
|
5
6
|
import Schema from "@deepseek-ai/schemastery";
|
|
6
7
|
|
|
7
8
|
if (typeof Schema.prototype?.volatile !== "function") {
|
|
@@ -217,6 +218,18 @@ export const Config = Schema.object({
|
|
|
217
218
|
auditFailClosed: Schema.boolean().default(false).volatile(),
|
|
218
219
|
/** Optional custom declarative rules YAML file path (#38). */
|
|
219
220
|
rulesFile: Schema.string().default("").volatile(),
|
|
221
|
+
/** Semantic LLM classifier endpoint URL (#127). */
|
|
222
|
+
classifierEndpoint: Schema.string().default("").volatile(),
|
|
223
|
+
/** API key for classifier endpoint (#115). */
|
|
224
|
+
classifierApiKey: Schema.string().default("").volatile(),
|
|
225
|
+
/** Model name for semantic classification. */
|
|
226
|
+
classifierModel: Schema.string().default("gpt-4o-mini").volatile(),
|
|
227
|
+
/** Request timeout in ms for classifier (#118). */
|
|
228
|
+
classifierTimeout: Schema.number().default(3000).volatile(),
|
|
229
|
+
/** Policy on classifier timeout: 'strict' or 'permissive' (#118). */
|
|
230
|
+
classifierTimeoutPolicy: Schema.union(["strict", "permissive"]).default("strict").volatile(),
|
|
231
|
+
/** Maximum context characters for classifier prompt (#117). */
|
|
232
|
+
classifierMaxContext: Schema.number().default(1000).volatile(),
|
|
220
233
|
/** Security engine instance (internal/testing). */
|
|
221
234
|
engine: Schema.any().hidden(),
|
|
222
235
|
});
|
|
@@ -256,9 +269,22 @@ export function apply(ctx, config) {
|
|
|
256
269
|
};
|
|
257
270
|
const unattendedPolicies = cfg.unattendedPolicies || DEFAULT_UNATTENDED_POLICIES.slice();
|
|
258
271
|
const forcedUnattended = cfg.unattended === true;
|
|
272
|
+
const classifier = cfg.classifierEndpoint
|
|
273
|
+
? new LlmClassifier({
|
|
274
|
+
endpoint: cfg.classifierEndpoint,
|
|
275
|
+
apiKey: cfg.classifierApiKey,
|
|
276
|
+
model: cfg.classifierModel,
|
|
277
|
+
timeoutMs: cfg.classifierTimeout,
|
|
278
|
+
timeoutPolicy: cfg.classifierTimeoutPolicy,
|
|
279
|
+
maxContextChars: cfg.classifierMaxContext,
|
|
280
|
+
})
|
|
281
|
+
: null;
|
|
282
|
+
|
|
259
283
|
const engine = (config && config.engine) || cfg.engine || new SecurityEngine({
|
|
260
284
|
inspectConfig: { toolName: toolName, fileWriteTools: fileWriteTools, workspaceDir: cfg.workspaceDir || "" },
|
|
261
285
|
bands: new BandsEngine({ toolRegistry: ctx && ctx.tools }),
|
|
286
|
+
classifier,
|
|
287
|
+
timeoutPolicy: cfg.classifierTimeoutPolicy || "strict",
|
|
262
288
|
defaultVerdict: cfg.defaultVerdict || "allow",
|
|
263
289
|
dryRun: Boolean(cfg.dryRun || cfg.shadowMode),
|
|
264
290
|
auditFailClosed: Boolean(cfg.auditFailClosed),
|
|
@@ -288,6 +314,8 @@ export function apply(ctx, config) {
|
|
|
288
314
|
active: true,
|
|
289
315
|
dryRun: Boolean(cfg.dryRun || cfg.shadowMode),
|
|
290
316
|
auditFailClosed: Boolean(cfg.auditFailClosed),
|
|
317
|
+
classifierActive: Boolean(classifier || engine.classifier),
|
|
318
|
+
timeoutPolicy: engine.timeoutPolicy || "strict",
|
|
291
319
|
counters: engine.getCounters(),
|
|
292
320
|
rulesCount: engine.declarativeRules ? engine.declarativeRules.count() : 0,
|
|
293
321
|
cacheStats: engine.cache ? engine.cache.stats() : null,
|
|
@@ -519,3 +547,11 @@ export {
|
|
|
519
547
|
WORKSPACE_RULES_REL,
|
|
520
548
|
USER_RULES_REL,
|
|
521
549
|
} from "./rules-loader.js";
|
|
550
|
+
export {
|
|
551
|
+
normalizeChatCompletionsUrl,
|
|
552
|
+
withLlmRetry,
|
|
553
|
+
sanitizeClassifierInput,
|
|
554
|
+
truncateClassifierContext,
|
|
555
|
+
isLowRiskExecution,
|
|
556
|
+
LlmClassifier,
|
|
557
|
+
} from "./classifier.js";
|
package/lib/messages.js
CHANGED
|
@@ -63,6 +63,10 @@ export const MESSAGES = Object.freeze({
|
|
|
63
63
|
ruleRetryExhaustion: "Exhausted repeated attempts to bypass safety gate",
|
|
64
64
|
ruleFailClosedAudit: "Audit log write failure blocked by fail-closed policy",
|
|
65
65
|
ruleLockedAskUnattended: "Critical operation requires human confirmation and cannot be bypassed in unattended mode",
|
|
66
|
+
ruleClassifierTimeoutStrict: "Semantic classifier timed out under strict confirmation policy",
|
|
67
|
+
ruleClassifierTimeoutPermissive: "Semantic classifier timed out; allowed by low-risk fallback policy",
|
|
68
|
+
askClassifierTimeout: "Semantic classifier request timed out; human confirmation required",
|
|
69
|
+
askClassifierError: "Semantic classifier error; human confirmation required",
|
|
66
70
|
hintRecursiveRm: "Specify exact target files instead of recursive deletion",
|
|
67
71
|
hintPathTraversal: "Keep operations strictly within the project directory",
|
|
68
72
|
hintSecretWrite: "Modify configuration via settings or environment variables, not raw file overwrite",
|
|
@@ -78,6 +82,7 @@ export const MESSAGES = Object.freeze({
|
|
|
78
82
|
hintRetryExhaustion: "Repeated denials detected; manual operator intervention required",
|
|
79
83
|
hintFailClosedAudit: "Ensure the audit log directory is writable and disk space is available",
|
|
80
84
|
hintLockedAskUnattended: "Switch to an interactive session or request explicit human confirmation",
|
|
85
|
+
hintClassifierTimeout: "Check external LLM connectivity or review timeout policy settings",
|
|
81
86
|
},
|
|
82
87
|
zh: {
|
|
83
88
|
blockedPrefix: "dsh-approval-gate 规则已阻止",
|
|
@@ -140,6 +145,10 @@ export const MESSAGES = Object.freeze({
|
|
|
140
145
|
ruleRetryExhaustion: "多次尝试绕过安全网关已达上限",
|
|
141
146
|
ruleFailClosedAudit: "审计日志写入失败,受安全失败策略阻止",
|
|
142
147
|
ruleLockedAskUnattended: "关键操作需要人工确认,无法在无人值守模式下自动批准",
|
|
148
|
+
ruleClassifierTimeoutStrict: "语义分类器在严格策略下超时",
|
|
149
|
+
ruleClassifierTimeoutPermissive: "语义分类器超时;根据低风险容错策略放行",
|
|
150
|
+
askClassifierTimeout: "语义分类器请求超时;需要人工审查",
|
|
151
|
+
askClassifierError: "语义分类器发生异常;需要人工审查",
|
|
143
152
|
hintRecursiveRm: "请指定具体目标文件,避免递归删除",
|
|
144
153
|
hintPathTraversal: "请将操作限制在项目根目录内",
|
|
145
154
|
hintSecretWrite: "请通过设置或环境变量修改配置,不要直接覆盖敏感配置文件",
|
|
@@ -155,6 +164,7 @@ export const MESSAGES = Object.freeze({
|
|
|
155
164
|
hintRetryExhaustion: "检测到重复拒绝;需要操作员手动介入",
|
|
156
165
|
hintFailClosedAudit: "请确保审计日志目录可写且磁盘空间充足",
|
|
157
166
|
hintLockedAskUnattended: "请切换到交互式会话或请求人工显式确认",
|
|
167
|
+
hintClassifierTimeout: "请检查外部 LLM 服务连通性或调整分类器超时容错策略",
|
|
158
168
|
},
|
|
159
169
|
});
|
|
160
170
|
|
|
@@ -217,6 +227,9 @@ const RULE_KEYS = Object.freeze({
|
|
|
217
227
|
"retry-exhaustion": "ruleRetryExhaustion",
|
|
218
228
|
"fail-closed-audit": "ruleFailClosedAudit",
|
|
219
229
|
"locked-ask-unattended-denied": "ruleLockedAskUnattended",
|
|
230
|
+
"classifier-timeout-strict": "ruleClassifierTimeoutStrict",
|
|
231
|
+
"classifier-timeout-fallback-strict": "ruleClassifierTimeoutStrict",
|
|
232
|
+
"classifier-timeout-fallback-permissive": "ruleClassifierTimeoutPermissive",
|
|
220
233
|
});
|
|
221
234
|
|
|
222
235
|
const HINT_KEYS = Object.freeze({
|
|
@@ -235,6 +248,8 @@ const HINT_KEYS = Object.freeze({
|
|
|
235
248
|
"retry-exhaustion": "hintRetryExhaustion",
|
|
236
249
|
"fail-closed-audit": "hintFailClosedAudit",
|
|
237
250
|
"locked-ask-unattended-denied": "hintLockedAskUnattended",
|
|
251
|
+
"classifier-timeout-strict": "hintClassifierTimeout",
|
|
252
|
+
"classifier-timeout-fallback-strict": "hintClassifierTimeout",
|
|
238
253
|
});
|
|
239
254
|
|
|
240
255
|
const ASK_KEYS = Object.freeze({
|
|
@@ -250,6 +265,9 @@ const ASK_KEYS = Object.freeze({
|
|
|
250
265
|
"credential-in-argv": "askCredentialInArgv",
|
|
251
266
|
"git-remote-push": "askGitRemotePush",
|
|
252
267
|
"silent-error-masked": "askSilentErrorMasked",
|
|
268
|
+
"classifier-timeout-strict": "askClassifierTimeout",
|
|
269
|
+
"classifier-timeout-fallback-strict": "askClassifierTimeout",
|
|
270
|
+
"classifier-error": "askClassifierError",
|
|
253
271
|
});
|
|
254
272
|
|
|
255
273
|
const DETAIL_KEYS = Object.freeze({
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@goodandready/dsh-approval-gate",
|
|
3
|
-
"version": "0.1.
|
|
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
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./lib/index.js",
|
|
9
|
-
"./package.json": "./package.json",
|
|
10
|
-
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"lib",
|
|
14
|
-
"rules",
|
|
15
|
-
"cordis.patch.yml",
|
|
16
|
-
"README.md",
|
|
17
|
-
"README.zh.md",
|
|
18
|
-
"README.ru.md",
|
|
19
|
-
"CHANGELOG.md",
|
|
20
|
-
"LICENSE"
|
|
21
|
-
],
|
|
22
|
-
"dsh": {
|
|
23
|
-
"bundle": {
|
|
24
|
-
"patch": "./cordis.patch.yml"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
30
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
31
|
-
},
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/GooDAnDReaDY/dsh-approval-gate.git"
|
|
35
|
-
},
|
|
36
|
-
"homepage": "https://github.com/GooDAnDReaDY/dsh-approval-gate#readme",
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/GooDAnDReaDY/dsh-approval-gate/issues"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"test": "node --test test/*.test.mjs"
|
|
42
|
-
},
|
|
43
|
-
"publishConfig": {
|
|
44
|
-
"registry": "https://registry.npmjs.org",
|
|
45
|
-
"access": "public"
|
|
46
|
-
},
|
|
47
|
-
"keywords": [
|
|
48
|
-
"dsh",
|
|
49
|
-
"dsh-plugin",
|
|
50
|
-
"deepseek-harness",
|
|
51
|
-
"security",
|
|
52
|
-
"approval-gate",
|
|
53
|
-
"safety"
|
|
54
|
-
]
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@goodandready/dsh-approval-gate",
|
|
3
|
+
"version": "0.1.17",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"rules",
|
|
15
|
+
"cordis.patch.yml",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.zh.md",
|
|
18
|
+
"README.ru.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"dsh": {
|
|
23
|
+
"bundle": {
|
|
24
|
+
"patch": "./cordis.patch.yml"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
30
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/GooDAnDReaDY/dsh-approval-gate.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/GooDAnDReaDY/dsh-approval-gate#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/GooDAnDReaDY/dsh-approval-gate/issues"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "node --test test/*.test.mjs"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"registry": "https://registry.npmjs.org",
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"dsh",
|
|
49
|
+
"dsh-plugin",
|
|
50
|
+
"deepseek-harness",
|
|
51
|
+
"security",
|
|
52
|
+
"approval-gate",
|
|
53
|
+
"safety"
|
|
54
|
+
]
|
|
55
|
+
}
|