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.
Files changed (77) hide show
  1. package/CHANGELOG.ja.md +82 -0
  2. package/CHANGELOG.ko.md +80 -0
  3. package/CHANGELOG.md +321 -0
  4. package/INSTALL.ja.md +70 -8
  5. package/INSTALL.ko.md +68 -9
  6. package/INSTALL.md +68 -8
  7. package/INSTALL.zh.md +59 -7
  8. package/README.ja.md +103 -16
  9. package/README.ko.md +101 -17
  10. package/README.md +152 -25
  11. package/README.zh.md +99 -12
  12. package/cordis.patch.yml +34 -3
  13. package/docs/baseline-guard.md +56 -0
  14. package/docs/plan-rules-to-settings.md +217 -0
  15. package/docs/rules-format.en.md +294 -0
  16. package/docs/rules-format.md +279 -0
  17. package/docs/ui-gap-analysis-and-branch-permissions.md +338 -0
  18. package/dsh.plugin.json +22 -0
  19. package/examples/permissions.example.yaml +16 -1
  20. package/lib/agent-identity.d.ts +43 -0
  21. package/lib/agent-identity.js +95 -0
  22. package/lib/allowlist.d.ts +27 -0
  23. package/lib/allowlist.js +103 -1
  24. package/lib/cli.js +11 -14
  25. package/lib/client.js +598 -6
  26. package/lib/client.js.map +1 -1
  27. package/lib/command-dispatcher.d.ts +22 -0
  28. package/lib/command-dispatcher.js +97 -0
  29. package/lib/command-semantics.d.ts +62 -0
  30. package/lib/command-semantics.js +6 -0
  31. package/lib/compiler.d.ts +50 -0
  32. package/lib/compiler.js +122 -0
  33. package/lib/config.d.ts +161 -7
  34. package/lib/config.js +78 -11
  35. package/lib/dry-run.d.ts +87 -0
  36. package/lib/dry-run.js +110 -0
  37. package/lib/engine.js +61 -4
  38. package/lib/evaluate.d.ts +9 -0
  39. package/lib/evaluate.js +264 -1
  40. package/lib/events.d.ts +69 -2
  41. package/lib/events.js +123 -0
  42. package/lib/index.d.ts +1 -1
  43. package/lib/index.js +320 -11
  44. package/lib/network-lifecycle.d.ts +107 -0
  45. package/lib/network-lifecycle.js +180 -0
  46. package/lib/network.d.ts +109 -0
  47. package/lib/network.js +196 -0
  48. package/lib/parsers/git.d.ts +43 -0
  49. package/lib/parsers/git.js +404 -0
  50. package/lib/parsers/shell-cmds.d.ts +32 -0
  51. package/lib/parsers/shell-cmds.js +293 -0
  52. package/lib/preset.d.ts +13 -0
  53. package/lib/preset.js +17 -0
  54. package/lib/proxy.d.ts +160 -0
  55. package/lib/proxy.js +529 -0
  56. package/lib/risk.d.ts +17 -3
  57. package/lib/risk.js +19 -6
  58. package/lib/rule-chain.d.ts +39 -0
  59. package/lib/rule-chain.js +213 -0
  60. package/lib/rule-dims.d.ts +174 -0
  61. package/lib/rule-dims.js +230 -0
  62. package/lib/rule.d.ts +60 -0
  63. package/lib/rule.js +69 -7
  64. package/lib/rules-view.d.ts +52 -0
  65. package/lib/rules-view.js +126 -0
  66. package/lib/runtime.d.ts +208 -7
  67. package/lib/runtime.js +433 -27
  68. package/lib/session-sweep.d.ts +45 -0
  69. package/lib/session-sweep.js +153 -0
  70. package/lib/shadow.d.ts +34 -0
  71. package/lib/shadow.js +143 -0
  72. package/lib/shell.d.ts +14 -0
  73. package/lib/shell.js +17 -0
  74. package/lib/watch.d.ts +42 -0
  75. package/lib/watch.js +170 -0
  76. package/package.json +15 -2
  77. 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;