dsh-perm-gate 5.1.2 → 5.3.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 (47) hide show
  1. package/CHANGELOG.ja.md +54 -1
  2. package/CHANGELOG.ko.md +54 -1
  3. package/CHANGELOG.md +134 -4
  4. package/README.ja.md +43 -0
  5. package/README.ko.md +43 -1
  6. package/README.md +73 -0
  7. package/README.zh.md +41 -0
  8. package/THIRD_PARTY_NOTICES.md +44 -0
  9. package/cordis.patch.yml +16 -1
  10. package/dsh.plugin.json +1 -1
  11. package/lib/ask-context.d.ts +49 -1
  12. package/lib/ask-context.js +89 -5
  13. package/lib/client.js +714 -86
  14. package/lib/client.js.map +1 -1
  15. package/lib/config.d.ts +243 -83
  16. package/lib/config.js +84 -41
  17. package/lib/credential-paths.d.ts +35 -0
  18. package/lib/credential-paths.js +119 -0
  19. package/lib/engine.d.ts +4 -6
  20. package/lib/engine.js +53 -42
  21. package/lib/evaluate.d.ts +20 -0
  22. package/lib/evaluate.js +41 -13
  23. package/lib/feedback.d.ts +42 -0
  24. package/lib/feedback.js +85 -0
  25. package/lib/index.d.ts +2 -2
  26. package/lib/index.js +143 -17
  27. package/lib/irreversible.js +109 -1
  28. package/lib/path.d.ts +31 -0
  29. package/lib/path.js +35 -0
  30. package/lib/preset-rules.d.ts +70 -0
  31. package/lib/preset-rules.js +388 -0
  32. package/lib/preset.d.ts +8 -5
  33. package/lib/preset.js +15 -6
  34. package/lib/risk.d.ts +29 -1
  35. package/lib/risk.js +36 -2
  36. package/lib/runtime.d.ts +164 -0
  37. package/lib/runtime.js +429 -55
  38. package/lib/scan-text.d.ts +69 -0
  39. package/lib/scan-text.js +162 -0
  40. package/lib/script-review.d.ts +50 -0
  41. package/lib/script-review.js +202 -0
  42. package/lib/shell.d.ts +18 -0
  43. package/lib/shell.js +103 -1
  44. package/lib/unattended.d.ts +61 -0
  45. package/lib/unattended.js +77 -0
  46. package/package.json +4 -3
  47. package/scripts/patch-permission-glyph.mjs +27 -13
package/lib/runtime.d.ts CHANGED
@@ -4,10 +4,12 @@ import type { ClassifierConfig } from './classifier.js';
4
4
  import { EventLog } from './events.js';
5
5
  import { RiskLearning } from './learning.js';
6
6
  import { type RiskRequest, type RiskVerdict } from './risk.js';
7
+ import { type AskContext } from './ask-context.js';
7
8
  import { type HostLlmLike, type HostModelSelection } from './host-llm.js';
8
9
  import { type CompiledRuleEntry, type CompiledRuleset, type RuleAction } from './rule.js';
9
10
  import type { NetworkTarget } from './network.js';
10
11
  import { type SessionEventLike } from './preset.js';
12
+ import { type UnattendedMode } from './unattended.js';
11
13
  /**
12
14
  * The gate's answer to the host's pre-execute waterfall.
13
15
  *
@@ -140,6 +142,26 @@ export interface PermGateRuntimeOptions extends PermGateConfig {
140
142
  readonly riskHook?: (req: RiskRequest) => Promise<RiskVerdict>;
141
143
  /** Optional live source of the Permissive tier (e.g. a DSH settings namespace). */
142
144
  readonly readPermissive?: () => PermissiveState;
145
+ /** Optional live source of the YOLO tier switch (never human-confirm; block-on-danger). */
146
+ readonly readYolo?: () => boolean;
147
+ /**
148
+ * Optional live source of the degraded-review switch: with no reviewer able to grade
149
+ * an ask, decide on the deterministic nets instead of parking it on a human.
150
+ */
151
+ readonly readRulesFallback?: () => boolean;
152
+ /**
153
+ * Optional live sources of the two `safenet` switches. Each says whether that
154
+ * implementation exists at all, and each owns exactly one preset (`permissive-mech` for
155
+ * pass, `permissive-snet` for ask), so flipping one cannot change another tier.
156
+ */
157
+ readonly readSafenetPass?: () => boolean;
158
+ readonly readSafenetAsk?: () => boolean;
159
+ /**
160
+ * Optional live source of the script-review switch: attach the body of every script a
161
+ * call runs to the review, so the grader and the human judge code, not a filename.
162
+ * Defaults to on (`config.scriptReview`).
163
+ */
164
+ readonly readScriptReview?: () => boolean;
143
165
  /** Optional live source of the llmAssist classifier setup. */
144
166
  readonly readClassifyConfig?: () => ClassifierConfig;
145
167
  /**
@@ -173,6 +195,23 @@ export interface PermGateRuntimeOptions extends PermGateConfig {
173
195
  * protective and never silently off); a non-empty array replaces the preset.
174
196
  */
175
197
  readonly readDenyKeywords?: () => readonly string[] | undefined;
198
+ /**
199
+ * Optional live source of the **appended** deny keywords. Unlike `readDenyKeywords`,
200
+ * which replaces the preset, this extends whatever list is live — the way to protect a
201
+ * long-tail secret without copying fifty preset entries into a profile.
202
+ */
203
+ readonly readDenyKeywordsExtra?: () => readonly string[] | undefined;
204
+ /**
205
+ * Optional live source of the user-declared credential paths, consulted by P0's read
206
+ * and mutation checks (see `config.credentialPaths`).
207
+ */
208
+ readonly readCredentialPaths?: () => readonly string[] | undefined;
209
+ /**
210
+ * Optional live source of the argument keys treated as document bodies / as operations,
211
+ * on top of the built-in lists (`extraContentArgKeys`, `extraOperationArgKeys`).
212
+ */
213
+ readonly readExtraContentArgKeys?: () => readonly string[] | undefined;
214
+ readonly readExtraOperationArgKeys?: () => readonly string[] | undefined;
176
215
  /**
177
216
  * Optional live source of the session's effective approval policy (`'ask'` /
178
217
  * `'never'`), normally `approval.effectivePolicy(agent.session)`. Under
@@ -409,6 +448,33 @@ export declare class PermGateRuntime {
409
448
  /** The host model-group selection, or undefined when unusable. */
410
449
  private hostModel;
411
450
  private livePermissive;
451
+ /** Whether YOLO mode is live (never human-confirm). */
452
+ isYolo(): boolean;
453
+ /** Whether degraded review is live (no reviewer ⇒ rule-only gate). */
454
+ isRulesFallback(): boolean;
455
+ /** Whether script-body review is live (default on). */
456
+ isScriptReview(): boolean;
457
+ /**
458
+ * The scripts one call will run, read for review.
459
+ *
460
+ * `bash ./clean-home.sh` names a file; the operation is inside it. So the body is
461
+ * fetched (bounded, never executing anything) and handed to the same three reviewers
462
+ * the rest of the call is: the deterministic nets judge it (an irreversible body is
463
+ * `I` no model may clear), the grader is shown it (`script_contents`), and the human
464
+ * card says it was attached. When *every* named script was read, the call is no longer
465
+ * an unreadable payload — that is what lifts a workspace-outside script run out of the
466
+ * `U` tier instead of parking it as unknowable.
467
+ *
468
+ * Best-effort by construction: any read problem yields "nothing attached", which the
469
+ * tier machinery already treats as opaque. It must never throw into the decision path.
470
+ */
471
+ private scriptReviewFor;
472
+ /**
473
+ * Whether a risk grader is reachable: an injected hook, the host `llm` service in
474
+ * host mode, or a configured custom endpoint. When false, YOLO decides purely on the
475
+ * deterministic danger signal (fail-open for a non-dangerous ask).
476
+ */
477
+ private classifierUsable;
412
478
  /**
413
479
  * The session's selected permission preset, folded from its event log. The
414
480
  * fold is cached on the log's length plus the identity of its last event: a
@@ -463,6 +529,16 @@ export declare class PermGateRuntime {
463
529
  * An empty override is treated as "no meaningful override" (the blacklist is
464
530
  * protective and stays on); schemastery materializes unset arrays as `[]`. */
465
531
  private liveDenyKeywords;
532
+ /** The user-declared credential paths, as configured (may be empty). */
533
+ private liveCredentialPaths;
534
+ /**
535
+ * The configured additions to the two argument-key sets, or `undefined` when nothing was
536
+ * configured so `scan-text` keeps its built-in pair without rebuilding anything.
537
+ *
538
+ * Read per call: both settings are volatile, and a scan rule that only applies after a
539
+ * reload is a scan rule nobody can reason about while debugging a false positive.
540
+ */
541
+ private liveScanKeys;
466
542
  /**
467
543
  * The preset deny-keyword layer (inherited from dsh-approval-gate): a keyword
468
544
  * hit over the call's command/argument text vetoes it with `deny` before any
@@ -615,6 +691,94 @@ export declare class PermGateRuntime {
615
691
  * settled by {@link settleExecution} when the call actually executes.
616
692
  */
617
693
  refineAsk(exec: ToolExecutionLike, decision: PreToolDecisionLike): Promise<PreToolDecisionLike | undefined>;
694
+ /**
695
+ * YOLO resolution of an `ask`: never raise a human. A **dangerous** ask — an
696
+ * irreversible / unlearnable / opaque call (deterministic), or an `llmAssist`
697
+ * hard-risk verdict when a grader is reachable — is **blocked** (`deny`), and the
698
+ * caller feeds the reason back to the main LLM as a user message. Every other ask
699
+ * is **allowed** (returns `undefined` → passthrough), so ordinary work keeps moving
700
+ * without a human. This is the mirror of `askAnswerable`'s never-passthrough (which
701
+ * blanket-allows under `approval: never`): YOLO allows the benign but actively
702
+ * intercepts the dangerous — "尽力自动拦截所有危险命令".
703
+ */
704
+ resolveYolo(exec: ToolExecutionLike, decision: PreToolDecisionLike): Promise<PreToolDecisionLike | undefined>;
705
+ /**
706
+ * The review model's pass over one ask, shared by every tier that lets a model
707
+ * speak for the human. `answered: false` means no reviewer is configured or it was
708
+ * unreachable — what that *means* is the one thing the tiers disagree about, so the
709
+ * caller decides (YOLO falls back to the mechanical conclusion, the human tiers keep
710
+ * the popup, the mechanical tier never gets here).
711
+ */
712
+ private modelJudge;
713
+ /**
714
+ * Which implementation, if any, owns this call's permission preset.
715
+ *
716
+ * One preset, one switch: `permissive-mech` is the mechanical takeover of asks,
717
+ * `permissive-snet` the model-free ask, `permissive-yolo` the model-backed human-free
718
+ * tier. Any other preset — including one whose switch is off — returns `null` and runs the
719
+ * ordinary flow, because a tier with no implementation behind it must not start behaving
720
+ * like some other tier.
721
+ */
722
+ unattendedModeOf(exec: ToolExecutionLike): UnattendedMode | null;
723
+ /** The mechanical human-free tier for this call (`permissive-mech`, switch on). */
724
+ isSafetyNet(exec: ToolExecutionLike): boolean;
725
+ /**
726
+ * The model-free ask tier for this call (`permissive-snet`, switch on): the mechanical
727
+ * screen still runs and its doubts ride along with the ask, but no grader is consulted
728
+ * and the decision stays with a person.
729
+ */
730
+ isSafetyNetAsk(exec: ToolExecutionLike): boolean;
731
+ /**
732
+ * One frozen view of a call for the human-free tiers: the same context the popup
733
+ * would have shown (script bodies included), plus the salted digest fields the
734
+ * grader path expects.
735
+ */
736
+ private unattendedContextFor;
737
+ /**
738
+ * The mechanical floor — what the deterministic nets can NAME about an ask, or `null`
739
+ * when nothing is nameable. Every tier classifies with this one judge; the tiers differ
740
+ * only in what they then do with the answer:
741
+ *
742
+ * | tier | 机械点名了危险 | 机械没点名 |
743
+ * | --- | --- | --- |
744
+ * | 人工确认(`permissive` / `permissive-full`) | 仍然问人,把信号作为疑点列出 | 问人(`llmAssist` 开着时模型可先精化) |
745
+ * | `safenetAsk`(`permissive-snet`) | 问人,列疑点 | 问人,**一次模型都不叫** |
746
+ * | `safenetPass`(`permissive-mech`) | 直接拦 + 回喂 | 直接放行,一次模型都不叫 |
747
+ * | `yolo`(`permissive-yolo`) | 直接拦(模型无法洗白,也不花这次调用) | 问模型;模型不在则按机械结论处置并标注 |
748
+ *
749
+ * The human tier not auto-blocking on a hit is deliberate: the same net that reads
750
+ * `rm -rf` of a home directory as irreversible also reads a project's own aggressive
751
+ * cleanup that way, and a person can tell those apart. Nobody is watching in the two
752
+ * human-free tiers, so there the cost of a false positive is accepted.
753
+ */
754
+ private mechanicalJudge;
755
+ /**
756
+ * The mechanical tier (`permissive-mech`, behind the `safenetPass` switch) — a
757
+ * human-free ask settled by the deterministic nets ALONE, with **no model call at all**.
758
+ * Two equivalent ways to name it: the reviewed form of `approval: never` (that preset
759
+ * passes every ask with nothing judging it; this one passes only what the nets cannot
760
+ * condemn), and YOLO with the model taken away — which is also what YOLO itself falls
761
+ * back to when no reviewer answers.
762
+ *
763
+ * It shares {@link mechanicalJudge} with every other tier's first pass: an opaque
764
+ * payload, irreversible act, or unlearnable destruction is blocked and the reason fed
765
+ * back to the main LLM. Everything the nets cannot name simply runs. It never softens a
766
+ * deterministic deny — those arrive already denied — and it never raises a human prompt.
767
+ */
768
+ resolveSafetyNet(exec: ToolExecutionLike, decision: PreToolDecisionLike): PreToolDecisionLike | undefined;
769
+ /**
770
+ * Degraded resolution of an `ask` when **no reviewer answered it** — either none is
771
+ * configured or the configured one could not reach a verdict. Instead of leaving the
772
+ * call on a human seam nobody is watching, it decides on the deterministic nets alone,
773
+ * the way a static policy gate does: an irreversible (`I`-tier) / unlearnable /
774
+ * opaque call is **blocked** (the caller feeds the reason back to the main LLM), and
775
+ * everything else runs. Unlike {@link resolveYolo} no model is consulted at all — this
776
+ * is what automatic review becomes when the model is missing, not a mode the operator
777
+ * picks while one is working. Never softens a deterministic deny: those arrive here
778
+ * already denied, and a rule-only `ask` is by construction something the rules could
779
+ * not condemn.
780
+ */
781
+ resolveRulesDegrade(exec: ToolExecutionLike, decision: PreToolDecisionLike, ctx?: AskContext): PreToolDecisionLike | undefined;
618
782
  /**
619
783
  * One frozen view of a call, built the same way for every ask path so the
620
784
  * popup cannot disagree with what the grader was shown.