@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/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.16",
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
+ }