dsh-perm-gate 2.1.0 → 3.0.0
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.ja.md +82 -0
- package/CHANGELOG.ko.md +80 -0
- package/CHANGELOG.md +321 -0
- package/INSTALL.ja.md +70 -8
- package/INSTALL.ko.md +68 -9
- package/INSTALL.md +68 -8
- package/INSTALL.zh.md +59 -7
- package/README.ja.md +103 -16
- package/README.ko.md +101 -17
- package/README.md +152 -25
- package/README.zh.md +99 -12
- package/cordis.patch.yml +34 -3
- package/docs/baseline-guard.md +56 -0
- package/docs/plan-rules-to-settings.md +217 -0
- package/docs/rules-format.en.md +294 -0
- package/docs/rules-format.md +279 -0
- package/docs/ui-gap-analysis-and-branch-permissions.md +338 -0
- package/dsh.plugin.json +22 -0
- package/examples/permissions.example.yaml +16 -1
- package/lib/agent-identity.d.ts +43 -0
- package/lib/agent-identity.js +95 -0
- package/lib/allowlist.d.ts +27 -0
- package/lib/allowlist.js +103 -1
- package/lib/cli.js +11 -14
- package/lib/client.js +598 -6
- package/lib/client.js.map +1 -1
- package/lib/command-dispatcher.d.ts +22 -0
- package/lib/command-dispatcher.js +97 -0
- package/lib/command-semantics.d.ts +62 -0
- package/lib/command-semantics.js +6 -0
- package/lib/compiler.d.ts +50 -0
- package/lib/compiler.js +122 -0
- package/lib/config.d.ts +161 -7
- package/lib/config.js +78 -11
- package/lib/dry-run.d.ts +87 -0
- package/lib/dry-run.js +110 -0
- package/lib/engine.js +61 -4
- package/lib/evaluate.d.ts +9 -0
- package/lib/evaluate.js +264 -1
- package/lib/events.d.ts +69 -2
- package/lib/events.js +123 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +320 -11
- package/lib/network-lifecycle.d.ts +107 -0
- package/lib/network-lifecycle.js +180 -0
- package/lib/network.d.ts +109 -0
- package/lib/network.js +196 -0
- package/lib/parsers/git.d.ts +43 -0
- package/lib/parsers/git.js +404 -0
- package/lib/parsers/shell-cmds.d.ts +32 -0
- package/lib/parsers/shell-cmds.js +293 -0
- package/lib/preset.d.ts +13 -0
- package/lib/preset.js +17 -0
- package/lib/proxy.d.ts +160 -0
- package/lib/proxy.js +529 -0
- package/lib/risk.d.ts +17 -3
- package/lib/risk.js +19 -6
- package/lib/rule-chain.d.ts +39 -0
- package/lib/rule-chain.js +213 -0
- package/lib/rule-dims.d.ts +174 -0
- package/lib/rule-dims.js +230 -0
- package/lib/rule.d.ts +60 -0
- package/lib/rule.js +69 -7
- package/lib/rules-view.d.ts +52 -0
- package/lib/rules-view.js +126 -0
- package/lib/runtime.d.ts +208 -7
- package/lib/runtime.js +433 -27
- package/lib/session-sweep.d.ts +45 -0
- package/lib/session-sweep.js +153 -0
- package/lib/shadow.d.ts +34 -0
- package/lib/shadow.js +143 -0
- package/lib/shell.d.ts +14 -0
- package/lib/shell.js +17 -0
- package/lib/watch.d.ts +42 -0
- package/lib/watch.js +170 -0
- package/package.json +15 -2
- package/scripts/patch-permission-glyph.mjs +209 -0
package/lib/events.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RulesView } from './rules-view.js';
|
|
1
2
|
/** One gate decision event. `kind` drives the client notice strip styling. */
|
|
2
3
|
export interface GateEvent {
|
|
3
4
|
/** Monotonic id (resumed from the file across restarts). */
|
|
@@ -8,9 +9,11 @@ export interface GateEvent {
|
|
|
8
9
|
/**
|
|
9
10
|
* auto = allowed (rule/grant/llm), ask = routed to the human, deny = vetoed,
|
|
10
11
|
* learned = confirmation settled, manual-* = the human's terminal answer to an
|
|
11
|
-
* ask (approved / rejected / cancelled)
|
|
12
|
+
* ask (approved / rejected / cancelled), stand-down = the gate itself is
|
|
13
|
+
* inactive in this session's permission preset and therefore decided nothing
|
|
14
|
+
* (see {@link GateEvent.verdict} `stand-down`).
|
|
12
15
|
*/
|
|
13
|
-
readonly kind: 'auto' | 'ask' | 'deny' | 'learned' | 'manual-approved' | 'manual-rejected' | 'manual-cancelled';
|
|
16
|
+
readonly kind: 'auto' | 'ask' | 'deny' | 'learned' | 'manual-approved' | 'manual-rejected' | 'manual-cancelled' | 'stand-down';
|
|
14
17
|
/** Risk category when an LLM verdict contributed to the decision. */
|
|
15
18
|
readonly risk?: string;
|
|
16
19
|
/** Truncated decision reason. */
|
|
@@ -127,11 +130,14 @@ export interface WebServerLike {
|
|
|
127
130
|
export declare const EVENTS_ROUTE = "/api/dsh-perm-gate/events";
|
|
128
131
|
export declare const LEARNING_ROUTE = "/api/dsh-perm-gate/learning";
|
|
129
132
|
export declare const HEALTH_ROUTE = "/api/dsh-perm-gate/health";
|
|
133
|
+
export declare const NETWORK_ROUTE = "/api/dsh-perm-gate/network";
|
|
130
134
|
export declare const RECEIVER_ROUTE = "/api/dsh-perm-gate/receiver";
|
|
131
135
|
export declare const DIFF_ROUTE = "/api/dsh-perm-gate/diff";
|
|
132
136
|
export declare const REVERT_ROUTE = "/api/dsh-perm-gate/revert";
|
|
133
137
|
export declare const SNAPSHOTS_STATS_ROUTE = "/api/dsh-perm-gate/snapshots-stats";
|
|
134
138
|
export declare const SNAPSHOTS_CLEAR_ROUTE = "/api/dsh-perm-gate/snapshots-clear";
|
|
139
|
+
export declare const DRY_RUN_ROUTE = "/api/dsh-perm-gate/dry-run";
|
|
140
|
+
export declare const RULES_ROUTE = "/api/dsh-perm-gate/rules";
|
|
135
141
|
/**
|
|
136
142
|
* Register `GET /api/dsh-perm-gate/events?sessionId=&since=` on the webServer
|
|
137
143
|
* service. Returns whether the route was registered (false when the service is
|
|
@@ -177,12 +183,73 @@ export declare function registerHealthRoute(server: unknown, provider: {
|
|
|
177
183
|
detail: string;
|
|
178
184
|
}>;
|
|
179
185
|
}): (() => void) | undefined;
|
|
186
|
+
/** The network-state face the settings UI's network section needs. */
|
|
187
|
+
export interface NetworkRouteProvider {
|
|
188
|
+
snapshot(): unknown;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Register the network diagnostics route: `GET /api/dsh-perm-gate/network`
|
|
192
|
+
* returns the live network state (mode, bind, port, proxy liveness, env
|
|
193
|
+
* injection, block counters, recent blocks). Read-only — a policy change goes
|
|
194
|
+
* through the settings namespace, never through HTTP.
|
|
195
|
+
* Returns whether the route was registered.
|
|
196
|
+
*/
|
|
197
|
+
export declare function registerNetworkRoute(server: unknown, provider: NetworkRouteProvider): (() => void) | undefined;
|
|
180
198
|
/** The learning-store face the settings UI's sediment view needs. */
|
|
181
199
|
export interface LearningRouteProvider {
|
|
182
200
|
snapshot(): unknown;
|
|
183
201
|
threshold(): number;
|
|
184
202
|
reset(key: string, fp?: string): void;
|
|
185
203
|
}
|
|
204
|
+
/** One rule-test request: the call to evaluate, nothing else. */
|
|
205
|
+
export interface DryRunRequest {
|
|
206
|
+
readonly tool: string;
|
|
207
|
+
readonly args: Record<string, unknown>;
|
|
208
|
+
readonly permissive?: boolean;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The rule-test face the settings card calls. `run` evaluates one call against
|
|
212
|
+
* the ruleset the gate currently has loaded and returns the report from
|
|
213
|
+
* `dryRunResult`. It must not change any state — see {@link registerDryRunRoute}.
|
|
214
|
+
*/
|
|
215
|
+
export interface DryRunRouteProvider {
|
|
216
|
+
run(request: DryRunRequest): unknown;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Register `POST /api/dsh-perm-gate/dry-run` — the rule-test panel's endpoint.
|
|
220
|
+
* Body `{ tool, args?, permissive? }`; the response carries the effective
|
|
221
|
+
* verdict plus the rule layer's own answer.
|
|
222
|
+
*
|
|
223
|
+
* **Read-only by construction.** Unlike `/learning` (GET reads, POST resets), this
|
|
224
|
+
* route has no write form: it never edits rules, grants, learning state or the
|
|
225
|
+
* settings namespace, and the provider it delegates to evaluates against a
|
|
226
|
+
* throwaway or live read path only. Testing a rule must not be able to change
|
|
227
|
+
* the ruleset — otherwise "test it first" would itself be the risky action.
|
|
228
|
+
*
|
|
229
|
+
* Returns whether the route was registered.
|
|
230
|
+
*/
|
|
231
|
+
export declare function registerDryRunRoute(server: unknown, provider: DryRunRouteProvider): (() => void) | undefined;
|
|
232
|
+
/**
|
|
233
|
+
* The permissions-file face the settings card calls. `view` reads the rules file
|
|
234
|
+
* the gate is loading; it must never write it.
|
|
235
|
+
*/
|
|
236
|
+
export interface RulesRouteProvider {
|
|
237
|
+
view(): RulesView;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Register `GET /api/dsh-perm-gate/rules` — the panel's view of the permissions
|
|
241
|
+
* YAML the gate actually loads.
|
|
242
|
+
*
|
|
243
|
+
* **Read-only by construction**, like {@link registerDryRunRoute}: there is no
|
|
244
|
+
* write form. The card could already *edit* this file — the allowlist section
|
|
245
|
+
* appends and replaces patterns — but had no way to display it, so an operator
|
|
246
|
+
* changing rules through the panel could not see the document being changed.
|
|
247
|
+
* Reading must not be the risky act, and a viewer that could write would make
|
|
248
|
+
* "let me just look at the rules" a mutation.
|
|
249
|
+
*
|
|
250
|
+
* Returns whether the route was registered.
|
|
251
|
+
*/
|
|
252
|
+
export declare function registerRulesRoute(server: unknown, provider: RulesRouteProvider): (() => void) | undefined;
|
|
186
253
|
/**
|
|
187
254
|
* Register the learning-store routes on the webServer service:
|
|
188
255
|
* `GET /api/dsh-perm-gate/learning` → the store snapshot + live threshold,
|
package/lib/events.js
CHANGED
|
@@ -351,11 +351,14 @@ function readBody(req, limit = 1024 * 1024) {
|
|
|
351
351
|
export const EVENTS_ROUTE = '/api/dsh-perm-gate/events';
|
|
352
352
|
export const LEARNING_ROUTE = '/api/dsh-perm-gate/learning';
|
|
353
353
|
export const HEALTH_ROUTE = '/api/dsh-perm-gate/health';
|
|
354
|
+
export const NETWORK_ROUTE = '/api/dsh-perm-gate/network';
|
|
354
355
|
export const RECEIVER_ROUTE = '/api/dsh-perm-gate/receiver';
|
|
355
356
|
export const DIFF_ROUTE = '/api/dsh-perm-gate/diff';
|
|
356
357
|
export const REVERT_ROUTE = '/api/dsh-perm-gate/revert';
|
|
357
358
|
export const SNAPSHOTS_STATS_ROUTE = '/api/dsh-perm-gate/snapshots-stats';
|
|
358
359
|
export const SNAPSHOTS_CLEAR_ROUTE = '/api/dsh-perm-gate/snapshots-clear';
|
|
360
|
+
export const DRY_RUN_ROUTE = '/api/dsh-perm-gate/dry-run';
|
|
361
|
+
export const RULES_ROUTE = '/api/dsh-perm-gate/rules';
|
|
359
362
|
/**
|
|
360
363
|
* Register `GET /api/dsh-perm-gate/events?sessionId=&since=` on the webServer
|
|
361
364
|
* service. Returns whether the route was registered (false when the service is
|
|
@@ -651,6 +654,126 @@ export function registerHealthRoute(server, provider) {
|
|
|
651
654
|
},
|
|
652
655
|
});
|
|
653
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Register the network diagnostics route: `GET /api/dsh-perm-gate/network`
|
|
659
|
+
* returns the live network state (mode, bind, port, proxy liveness, env
|
|
660
|
+
* injection, block counters, recent blocks). Read-only — a policy change goes
|
|
661
|
+
* through the settings namespace, never through HTTP.
|
|
662
|
+
* Returns whether the route was registered.
|
|
663
|
+
*/
|
|
664
|
+
export function registerNetworkRoute(server, provider) {
|
|
665
|
+
const ws = routeServer(server);
|
|
666
|
+
if (ws === undefined)
|
|
667
|
+
return undefined;
|
|
668
|
+
return ws.register({
|
|
669
|
+
kind: 'exact',
|
|
670
|
+
path: NETWORK_ROUTE,
|
|
671
|
+
handler: (rawReq, rawRes) => {
|
|
672
|
+
const req = rawReq;
|
|
673
|
+
const res = rawRes;
|
|
674
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
675
|
+
json(res, 405, { ok: false, error: 'method not allowed' });
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
json(res, 200, { ok: true, ...provider.snapshot() });
|
|
680
|
+
}
|
|
681
|
+
catch (e) {
|
|
682
|
+
json(res, 500, { ok: false, error: String(e?.message ?? e) });
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Register `POST /api/dsh-perm-gate/dry-run` — the rule-test panel's endpoint.
|
|
689
|
+
* Body `{ tool, args?, permissive? }`; the response carries the effective
|
|
690
|
+
* verdict plus the rule layer's own answer.
|
|
691
|
+
*
|
|
692
|
+
* **Read-only by construction.** Unlike `/learning` (GET reads, POST resets), this
|
|
693
|
+
* route has no write form: it never edits rules, grants, learning state or the
|
|
694
|
+
* settings namespace, and the provider it delegates to evaluates against a
|
|
695
|
+
* throwaway or live read path only. Testing a rule must not be able to change
|
|
696
|
+
* the ruleset — otherwise "test it first" would itself be the risky action.
|
|
697
|
+
*
|
|
698
|
+
* Returns whether the route was registered.
|
|
699
|
+
*/
|
|
700
|
+
export function registerDryRunRoute(server, provider) {
|
|
701
|
+
const ws = routeServer(server);
|
|
702
|
+
if (ws === undefined)
|
|
703
|
+
return undefined;
|
|
704
|
+
return ws.register({
|
|
705
|
+
kind: 'exact',
|
|
706
|
+
path: DRY_RUN_ROUTE,
|
|
707
|
+
handler: (rawReq, rawRes) => {
|
|
708
|
+
const req = rawReq;
|
|
709
|
+
const res = rawRes;
|
|
710
|
+
if (req.method !== 'POST') {
|
|
711
|
+
json(res, 405, { ok: false, error: 'method not allowed' });
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
void (async () => {
|
|
715
|
+
try {
|
|
716
|
+
const parsed = await readBody(req);
|
|
717
|
+
const tool = parsed.tool;
|
|
718
|
+
if (typeof tool !== 'string' || tool === '') {
|
|
719
|
+
json(res, 400, { ok: false, error: 'missing tool' });
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const args = parsed.args;
|
|
723
|
+
if (args !== undefined && (typeof args !== 'object' || args === null || Array.isArray(args))) {
|
|
724
|
+
json(res, 400, { ok: false, error: 'args must be an object' });
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const result = provider.run({
|
|
728
|
+
tool,
|
|
729
|
+
args: (args ?? {}),
|
|
730
|
+
...(typeof parsed.permissive === 'boolean' ? { permissive: parsed.permissive } : {}),
|
|
731
|
+
});
|
|
732
|
+
json(res, 200, { ok: true, result });
|
|
733
|
+
}
|
|
734
|
+
catch (e) {
|
|
735
|
+
json(res, 500, { ok: false, error: String(e?.message ?? e) });
|
|
736
|
+
}
|
|
737
|
+
})();
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Register `GET /api/dsh-perm-gate/rules` — the panel's view of the permissions
|
|
743
|
+
* YAML the gate actually loads.
|
|
744
|
+
*
|
|
745
|
+
* **Read-only by construction**, like {@link registerDryRunRoute}: there is no
|
|
746
|
+
* write form. The card could already *edit* this file — the allowlist section
|
|
747
|
+
* appends and replaces patterns — but had no way to display it, so an operator
|
|
748
|
+
* changing rules through the panel could not see the document being changed.
|
|
749
|
+
* Reading must not be the risky act, and a viewer that could write would make
|
|
750
|
+
* "let me just look at the rules" a mutation.
|
|
751
|
+
*
|
|
752
|
+
* Returns whether the route was registered.
|
|
753
|
+
*/
|
|
754
|
+
export function registerRulesRoute(server, provider) {
|
|
755
|
+
const ws = routeServer(server);
|
|
756
|
+
if (ws === undefined)
|
|
757
|
+
return undefined;
|
|
758
|
+
return ws.register({
|
|
759
|
+
kind: 'exact',
|
|
760
|
+
path: RULES_ROUTE,
|
|
761
|
+
handler: (rawReq, rawRes) => {
|
|
762
|
+
const req = rawReq;
|
|
763
|
+
const res = rawRes;
|
|
764
|
+
if (req.method !== 'GET') {
|
|
765
|
+
json(res, 405, { ok: false, error: 'method not allowed' });
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
json(res, 200, { ok: true, result: provider.view() });
|
|
770
|
+
}
|
|
771
|
+
catch (e) {
|
|
772
|
+
json(res, 500, { ok: false, error: String(e?.message ?? e) });
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
}
|
|
654
777
|
/**
|
|
655
778
|
* Register the learning-store routes on the webServer service:
|
|
656
779
|
* `GET /api/dsh-perm-gate/learning` → the store snapshot + live threshold,
|
package/lib/index.d.ts
CHANGED
|
@@ -53,5 +53,5 @@ export declare function makeApprovalAnswerer(runtime: Pick<PermGateRuntime, 'set
|
|
|
53
53
|
* `riskTimeoutMs` (default 20 s), and a grader failure keeps the original ask
|
|
54
54
|
* (fail-closed).
|
|
55
55
|
*/
|
|
56
|
-
export declare function makePreExecuteListener(runtime: Pick<PermGateRuntime, 'decideExecution' | 'refineAsk'>): (exec: ToolExecutionLike, next: () => Promise<unknown>) => Promise<unknown>;
|
|
56
|
+
export declare function makePreExecuteListener(runtime: Pick<PermGateRuntime, 'decideExecution' | 'refineAsk' | 'beginShellExecution'>): (exec: ToolExecutionLike, next: () => Promise<unknown>) => Promise<unknown>;
|
|
57
57
|
export declare function apply(ctx: Context, config?: Record<string, unknown>): PermGateRuntime;
|