dsh-perm-gate 5.4.1 → 5.4.3
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 +13 -0
- package/README.md +3 -1
- package/README.zh.md +1 -1
- package/dsh.plugin.json +1 -1
- package/lib/client.js +64 -0
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +22 -6
- package/lib/config.js +6 -3
- package/lib/events.js +8 -1
- package/lib/host-llm.d.ts +19 -2
- package/lib/host-llm.js +31 -8
- package/lib/index.d.ts +46 -0
- package/lib/index.js +126 -20
- package/lib/risk.js +5 -7
- package/lib/rule.d.ts +11 -0
- package/lib/rule.js +13 -0
- package/lib/runtime.d.ts +27 -1
- package/lib/runtime.js +40 -11
- package/package.json +1 -1
package/lib/runtime.js
CHANGED
|
@@ -28,7 +28,7 @@ import { redactCommandText } from './redact.js';
|
|
|
28
28
|
import { executableVariants, normalizeCommandText } from './command-normalize.js';
|
|
29
29
|
import { completeViaHost, DEFAULT_HOST_MODEL } from './host-llm.js';
|
|
30
30
|
import { chatCompletion } from './classifier.js';
|
|
31
|
-
import { compileDocument, compileRulesObject, documentHash, extractPathCandidates, parsePermissionsDocument, } from './rule.js';
|
|
31
|
+
import { coerceRuleAction, compileDocument, compileRulesObject, documentHash, extractPathCandidates, parsePermissionsDocument, } from './rule.js';
|
|
32
32
|
import { decomposeShellCommand } from './shell.js';
|
|
33
33
|
import { extractAgentCandidates } from './agent-identity.js';
|
|
34
34
|
import { resolveRuleChain } from './rule-chain.js';
|
|
@@ -289,10 +289,17 @@ export class PermGateRuntime {
|
|
|
289
289
|
}
|
|
290
290
|
compileInline(options) {
|
|
291
291
|
const empty = {
|
|
292
|
-
defaultAction: options
|
|
292
|
+
defaultAction: this.effectiveDefaultAction(options),
|
|
293
293
|
deny: [], allow: [], ask: [],
|
|
294
294
|
caseInsensitivePaths: options.caseInsensitivePaths ?? true,
|
|
295
295
|
};
|
|
296
|
+
// Every other path yields a ruleset whose default came from a document; a
|
|
297
|
+
// document is validated, but the action is still coerced so one bad source
|
|
298
|
+
// can never hand the runtime an action it has no branch for.
|
|
299
|
+
const settle = (rs) => {
|
|
300
|
+
const action = coerceRuleAction(rs.defaultAction);
|
|
301
|
+
return action === rs.defaultAction ? rs : { ...rs, defaultAction: action };
|
|
302
|
+
};
|
|
296
303
|
// Settings-first (dual-source): a configured `dsh-perm-gate-rules`
|
|
297
304
|
// namespace overrides every file path — the gate then loads with zero
|
|
298
305
|
// filesystem dependency. Bare defaults (nothing configured) fall through
|
|
@@ -303,20 +310,20 @@ export class PermGateRuntime {
|
|
|
303
310
|
const hash = documentHash(JSON.stringify(settingsDoc));
|
|
304
311
|
const hit = this.cache.get(hash);
|
|
305
312
|
if (hit !== undefined)
|
|
306
|
-
return hit;
|
|
313
|
+
return settle(hit);
|
|
307
314
|
const compiled = compileRulesObject(settingsDoc, {
|
|
308
315
|
maxGlobStars: 2,
|
|
309
316
|
caseInsensitivePaths: options.caseInsensitivePaths ?? true,
|
|
310
317
|
});
|
|
311
318
|
this.cache.set(hash, compiled);
|
|
312
|
-
return compiled;
|
|
319
|
+
return settle(compiled);
|
|
313
320
|
}
|
|
314
321
|
// Chain mode: when searchUp is enabled, use the multi-file chain resolver.
|
|
315
322
|
if (options.searchUp === true) {
|
|
316
323
|
const cwd = options.cwd ?? process.cwd();
|
|
317
324
|
const rulesFile = options.rulesFile !== undefined ? options.rulesFile : 'rules.yml';
|
|
318
325
|
try {
|
|
319
|
-
return resolveRuleChain(cwd, {
|
|
326
|
+
return settle(resolveRuleChain(cwd, {
|
|
320
327
|
rulesFile,
|
|
321
328
|
searchUp: true,
|
|
322
329
|
fallbackPath: options.fallbackPath,
|
|
@@ -325,7 +332,7 @@ export class PermGateRuntime {
|
|
|
325
332
|
}, {
|
|
326
333
|
maxGlobStars: 2,
|
|
327
334
|
caseInsensitivePaths: options.caseInsensitivePaths ?? true,
|
|
328
|
-
});
|
|
335
|
+
}));
|
|
329
336
|
}
|
|
330
337
|
catch {
|
|
331
338
|
return empty;
|
|
@@ -338,14 +345,27 @@ export class PermGateRuntime {
|
|
|
338
345
|
const hash = documentHash(text);
|
|
339
346
|
const hit = this.cache.get(hash);
|
|
340
347
|
if (hit !== undefined)
|
|
341
|
-
return hit;
|
|
348
|
+
return settle(hit);
|
|
342
349
|
const doc = parsePermissionsDocument(text);
|
|
343
350
|
const compiled = compileDocument(doc, {
|
|
344
351
|
maxGlobStars: 2,
|
|
345
352
|
caseInsensitivePaths: options.caseInsensitivePaths ?? true,
|
|
346
353
|
});
|
|
347
354
|
this.cache.set(hash, compiled);
|
|
348
|
-
return compiled;
|
|
355
|
+
return settle(compiled);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* The root default action, read live (the config field is volatile) and
|
|
359
|
+
* validated. An unrecognised value — most often a `.volatile()` ref that
|
|
360
|
+
* reached the options un-unwrapped — fails **closed** to `ask`: previously it
|
|
361
|
+
* became the ruleset default verbatim, both the deny and ask branches missed
|
|
362
|
+
* it, and every unmatched call was allowed without review (issue #6).
|
|
363
|
+
*/
|
|
364
|
+
effectiveDefaultAction(options) {
|
|
365
|
+
const raw = options.readDefaultAction !== undefined
|
|
366
|
+
? options.readDefaultAction()
|
|
367
|
+
: options.defaultAction;
|
|
368
|
+
return coerceRuleAction(raw);
|
|
349
369
|
}
|
|
350
370
|
/** Reload rules from disk (HMR); on failure keeps the previous rules. */
|
|
351
371
|
reload() {
|
|
@@ -659,6 +679,15 @@ export class PermGateRuntime {
|
|
|
659
679
|
const sel = this.options.readHostModel?.();
|
|
660
680
|
return sel !== undefined && sel.provider !== '' && sel.model !== '' ? sel : undefined;
|
|
661
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* Reasoning effort for host reviews. `undefined` **omits** the field so the host
|
|
684
|
+
* model configuration decides; the old hardcoded `'off'` was rejected by host
|
|
685
|
+
* validation for models whose advertised efforts exclude it (issue #8).
|
|
686
|
+
*/
|
|
687
|
+
hostEffort() {
|
|
688
|
+
const effort = this.options.readClassifyEffort?.();
|
|
689
|
+
return typeof effort === 'string' && effort !== '' ? effort : undefined;
|
|
690
|
+
}
|
|
662
691
|
livePermissive() {
|
|
663
692
|
return this.options.readPermissive !== undefined
|
|
664
693
|
? this.options.readPermissive()
|
|
@@ -1422,7 +1451,7 @@ export class PermGateRuntime {
|
|
|
1422
1451
|
}
|
|
1423
1452
|
else if (useHost) {
|
|
1424
1453
|
const selection = this.hostModel() ?? DEFAULT_HOST_MODEL;
|
|
1425
|
-
risk = await classifyRiskWith((system, user) => completeViaHost(this.options.hostLlm, selection, system, user, cfg?.timeoutMs ?? 20_000), req);
|
|
1454
|
+
risk = await classifyRiskWith((system, user) => completeViaHost(this.options.hostLlm, selection, system, user, cfg?.timeoutMs ?? 20_000, { effort: this.hostEffort() }), req);
|
|
1426
1455
|
}
|
|
1427
1456
|
else {
|
|
1428
1457
|
risk = await classifyRisk(cfg ?? {}, req);
|
|
@@ -1624,7 +1653,7 @@ export class PermGateRuntime {
|
|
|
1624
1653
|
}
|
|
1625
1654
|
else if (useHost) {
|
|
1626
1655
|
const selection = this.hostModel() ?? DEFAULT_HOST_MODEL;
|
|
1627
|
-
risk = await classifyRiskWith((system, user) => completeViaHost(this.options.hostLlm, selection, system, user, cfg?.timeoutMs ?? 20_000), req);
|
|
1656
|
+
risk = await classifyRiskWith((system, user) => completeViaHost(this.options.hostLlm, selection, system, user, cfg?.timeoutMs ?? 20_000, { effort: this.hostEffort() }), req);
|
|
1628
1657
|
}
|
|
1629
1658
|
else {
|
|
1630
1659
|
risk = await classifyRisk(cfg ?? {}, req);
|
|
@@ -2010,7 +2039,7 @@ export class PermGateRuntime {
|
|
|
2010
2039
|
if (this.options.hostLlm === undefined)
|
|
2011
2040
|
return done(false, 'host llm service unavailable');
|
|
2012
2041
|
const selection = this.hostModel() ?? DEFAULT_HOST_MODEL;
|
|
2013
|
-
const r = await completeViaHost(this.options.hostLlm, selection, 'Reply with exactly: OK', 'ping', cfg?.timeoutMs ?? 20_000);
|
|
2042
|
+
const r = await completeViaHost(this.options.hostLlm, selection, 'Reply with exactly: OK', 'ping', cfg?.timeoutMs ?? 20_000, { effort: this.hostEffort() });
|
|
2014
2043
|
return r.ok ? done(true, `${selection.provider}/${selection.model} → ${r.content.trim().slice(0, 60) || 'ok'}`) : done(false, r.error ?? 'host llm call failed');
|
|
2015
2044
|
}
|
|
2016
2045
|
if (cfg?.endpoint === undefined || cfg.endpoint === '' || cfg?.model === undefined || cfg.model === '') {
|