dsh-perm-gate 4.2.0-beta.5 → 4.2.0-beta.6
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/dsh.plugin.json +1 -1
- package/lib/allowlist.d.ts +5 -3
- package/lib/allowlist.js +11 -4
- package/lib/config.d.ts +6 -3
- package/lib/config.js +10 -3
- package/lib/index.js +13 -1
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-perm-gate",
|
|
3
3
|
"name": "dsh-perm-gate",
|
|
4
|
-
"version": "4.2.0-beta.
|
|
4
|
+
"version": "4.2.0-beta.6",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Deterministic-first, fail-closed permission gate for DeepSeek Harness 0.1.2+: a P0 hard-deny -> P1 precise session grant -> P2 static allow/deny rule chain -> P3 optional LLM classifier -> P4 ask pipeline over the tools/pre-execute waterfall, with a standalone CLI dry-run evaluator and an auditable decision log. It only vetoes with deny/ask and always delegates otherwise, and unknown or malformed rules fail loud instead of silently standing the gate down.",
|
|
7
7
|
"zh": "面向 DeepSeek Harness 0.1.2+ 的确定性优先、失败即拒的权限闸门:在 tools/pre-execute 瀑布上串起 P0 硬拒绝 → P1 精确会话授权 → P2 静态 allow/deny 规则链 → P3 可选 LLM 语义分类 → P4 ask,并附带独立 CLI 干跑评估器与可审计的决策日志。只以 deny/ask 否决、其余一律放行,规则未知或非法时直接报错,绝不静默失效。"
|
package/lib/allowlist.d.ts
CHANGED
|
@@ -46,8 +46,10 @@ export declare function listAllowCommands(rulesFile: string): string[];
|
|
|
46
46
|
export declare function replaceAllowCommands(rulesFile: string, patterns: readonly string[], reason?: string): boolean;
|
|
47
47
|
/**
|
|
48
48
|
* Parse the rules file into a plain document — the seed for the first
|
|
49
|
-
* settings-backed allowlist write
|
|
50
|
-
* the entry's `rules` config field
|
|
51
|
-
*
|
|
49
|
+
* settings-backed allowlist write and the activation-time rules seed, so
|
|
50
|
+
* migrating a file-defined document into the entry's `rules` config field
|
|
51
|
+
* cannot drop its deny/ask sections. The file's optional `permissions:`
|
|
52
|
+
* wrapper is unwrapped (the config field stores the bare form). Returns `{}`
|
|
53
|
+
* when the file is absent or unreadable.
|
|
52
54
|
*/
|
|
53
55
|
export declare function readRulesFileDoc(rulesFile: string): unknown;
|
package/lib/allowlist.js
CHANGED
|
@@ -199,16 +199,23 @@ function readFileSafe(p) {
|
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
201
|
* Parse the rules file into a plain document — the seed for the first
|
|
202
|
-
* settings-backed allowlist write
|
|
203
|
-
* the entry's `rules` config field
|
|
204
|
-
*
|
|
202
|
+
* settings-backed allowlist write and the activation-time rules seed, so
|
|
203
|
+
* migrating a file-defined document into the entry's `rules` config field
|
|
204
|
+
* cannot drop its deny/ask sections. The file's optional `permissions:`
|
|
205
|
+
* wrapper is unwrapped (the config field stores the bare form). Returns `{}`
|
|
206
|
+
* when the file is absent or unreadable.
|
|
205
207
|
*/
|
|
206
208
|
export function readRulesFileDoc(rulesFile) {
|
|
207
209
|
const text = readFileSafe(rulesFile);
|
|
208
210
|
if (text === '')
|
|
209
211
|
return {};
|
|
210
212
|
try {
|
|
211
|
-
|
|
213
|
+
const root = parse(text);
|
|
214
|
+
if (root !== null && typeof root === 'object' && !Array.isArray(root)
|
|
215
|
+
&& root['permissions'] !== null && typeof root['permissions'] === 'object') {
|
|
216
|
+
return root['permissions'];
|
|
217
|
+
}
|
|
218
|
+
return root ?? {};
|
|
212
219
|
}
|
|
213
220
|
catch {
|
|
214
221
|
return {};
|
package/lib/config.d.ts
CHANGED
|
@@ -277,14 +277,17 @@ export interface RulesConfig {
|
|
|
277
277
|
readonly deny?: unknown[];
|
|
278
278
|
readonly allow?: unknown[];
|
|
279
279
|
readonly ask?: unknown[];
|
|
280
|
+
/** Activation-time seed marker (see RulesSchema). */
|
|
281
|
+
readonly initialized?: boolean;
|
|
280
282
|
}
|
|
281
283
|
/** Settings namespace for rules (separate from the main perm-gate namespace). */
|
|
282
284
|
export declare const RULES_NAMESPACE = "dsh-perm-gate-rules";
|
|
283
285
|
/**
|
|
284
286
|
* Whether a settings-sourced rules document carries a REAL configuration —
|
|
285
|
-
* entries
|
|
286
|
-
*
|
|
287
|
-
*
|
|
287
|
+
* entries, a non-default `defaultAction`, or the `initialized` marker the
|
|
288
|
+
* activation-time seed writes after populating an empty document. A namespace
|
|
289
|
+
* still holding bare schema defaults is "not configured" and must not shadow
|
|
290
|
+
* the rules file (the dual-source contract: settings first, file fallback).
|
|
288
291
|
*/
|
|
289
292
|
export declare function isRulesConfigured(value: unknown): boolean;
|
|
290
293
|
/**
|
package/lib/config.js
CHANGED
|
@@ -113,19 +113,26 @@ export const RulesSchema = z.object({
|
|
|
113
113
|
deny: z.array(RuleEntrySchema).default([]),
|
|
114
114
|
allow: z.array(RuleEntrySchema).default([]),
|
|
115
115
|
ask: z.array(RuleEntrySchema).default([]),
|
|
116
|
+
// Activation-time seed marker: the gate writes `true` the first time it
|
|
117
|
+
// populates an empty document (file migration or bare starter), so the
|
|
118
|
+
// seeded namespace becomes the rules source instead of shadowing nothing.
|
|
119
|
+
initialized: z.boolean().default(false),
|
|
116
120
|
});
|
|
117
121
|
/** Settings namespace for rules (separate from the main perm-gate namespace). */
|
|
118
122
|
export const RULES_NAMESPACE = 'dsh-perm-gate-rules';
|
|
119
123
|
/**
|
|
120
124
|
* Whether a settings-sourced rules document carries a REAL configuration —
|
|
121
|
-
* entries
|
|
122
|
-
*
|
|
123
|
-
*
|
|
125
|
+
* entries, a non-default `defaultAction`, or the `initialized` marker the
|
|
126
|
+
* activation-time seed writes after populating an empty document. A namespace
|
|
127
|
+
* still holding bare schema defaults is "not configured" and must not shadow
|
|
128
|
+
* the rules file (the dual-source contract: settings first, file fallback).
|
|
124
129
|
*/
|
|
125
130
|
export function isRulesConfigured(value) {
|
|
126
131
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
127
132
|
return false;
|
|
128
133
|
const doc = value;
|
|
134
|
+
if (doc.initialized === true)
|
|
135
|
+
return true;
|
|
129
136
|
if (doc.defaultAction !== undefined && doc.defaultAction !== 'ask')
|
|
130
137
|
return true;
|
|
131
138
|
for (const key of ['deny', 'allow', 'ask']) {
|
package/lib/index.js
CHANGED
|
@@ -188,7 +188,19 @@ export function apply(ctx, config = {}) {
|
|
|
188
188
|
const ENTRY_ID = 'dsh-perm-gate';
|
|
189
189
|
const settingsRef = {};
|
|
190
190
|
ctx.inject(['settings'], (child) => {
|
|
191
|
-
|
|
191
|
+
const svc = child.settings;
|
|
192
|
+
settingsRef.svc = svc;
|
|
193
|
+
// Activation-time seed: a never-configured `rules` field is populated once
|
|
194
|
+
// — from the rules file when one exists (implicit migration), otherwise a
|
|
195
|
+
// bare starter document — and marked `initialized`, so the panel shows a
|
|
196
|
+
// live rules document from the first boot instead of a missing-file
|
|
197
|
+
// fallback. The field watcher below picks the seed up via reload.
|
|
198
|
+
if (svc !== undefined && readRulesDocument() === undefined) {
|
|
199
|
+
const seed = { ...readRulesFileDoc(rulesFilePath), initialized: true };
|
|
200
|
+
void svc.update(ENTRY_ID, { rules: seed }).catch((e) => {
|
|
201
|
+
console.warn('[dsh-perm-gate] rules seed write failed:', e);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
192
204
|
});
|
|
193
205
|
const settingsWrite = (write) => {
|
|
194
206
|
const svc = settingsRef.svc;
|