dsh-perm-gate 0.2.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 (53) hide show
  1. package/CHANGELOG.ja.md +62 -0
  2. package/CHANGELOG.ko.md +60 -0
  3. package/CHANGELOG.md +62 -0
  4. package/INSTALL.ja.md +152 -0
  5. package/INSTALL.ko.md +151 -0
  6. package/INSTALL.md +154 -0
  7. package/INSTALL.zh.md +147 -0
  8. package/LICENSE +21 -0
  9. package/README.ja.md +197 -0
  10. package/README.ko.md +195 -0
  11. package/README.md +209 -0
  12. package/README.zh.md +144 -0
  13. package/assets/.gitkeep +0 -0
  14. package/assets/README.md +5 -0
  15. package/cordis.patch.yml +35 -0
  16. package/examples/permissions.example.yaml +20 -0
  17. package/lib/allowlist.d.ts +19 -0
  18. package/lib/allowlist.js +97 -0
  19. package/lib/audit.d.ts +45 -0
  20. package/lib/audit.js +34 -0
  21. package/lib/classifier.d.ts +74 -0
  22. package/lib/classifier.js +129 -0
  23. package/lib/cli.d.ts +2 -0
  24. package/lib/cli.js +67 -0
  25. package/lib/client.js +859 -0
  26. package/lib/client.js.map +1 -0
  27. package/lib/compiler.d.ts +30 -0
  28. package/lib/compiler.js +126 -0
  29. package/lib/config.d.ts +69 -0
  30. package/lib/config.js +64 -0
  31. package/lib/engine.d.ts +17 -0
  32. package/lib/engine.js +85 -0
  33. package/lib/evaluate.d.ts +20 -0
  34. package/lib/evaluate.js +94 -0
  35. package/lib/events.d.ts +56 -0
  36. package/lib/events.js +129 -0
  37. package/lib/grant.d.ts +60 -0
  38. package/lib/grant.js +121 -0
  39. package/lib/index.d.ts +17 -0
  40. package/lib/index.js +140 -0
  41. package/lib/learning.d.ts +58 -0
  42. package/lib/learning.js +181 -0
  43. package/lib/path.d.ts +47 -0
  44. package/lib/path.js +94 -0
  45. package/lib/risk.d.ts +58 -0
  46. package/lib/risk.js +100 -0
  47. package/lib/rule.d.ts +64 -0
  48. package/lib/rule.js +220 -0
  49. package/lib/runtime.d.ts +146 -0
  50. package/lib/runtime.js +350 -0
  51. package/lib/shell.d.ts +38 -0
  52. package/lib/shell.js +176 -0
  53. package/package.json +120 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["useState","useSyncExternalStore","useState","useRef"],"sources":["../src/client/locales.ts","../src/client/card.tsx","../src/client/permission-icon.ts","../src/client/notice.tsx","../src/client/index.ts"],"sourcesContent":["/** `dsh-perm-gate` client dictionaries (zh / en / ja / ko). */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'dsh-perm-gate'\n\n/** The key union of this plugin's dictionary (source of truth: `zh`). */\nexport type PermissiveKey = keyof typeof zh\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'card.title': 'Permissive 审批档',\n 'card.description': '独立审批模式档位:与只读 / 完全权限 / 白名单平行。前端只暴露一个开关;后台三个审批策略可组合、由此处设置决定,仍对 P0 硬拒绝保持 fail-closed。',\n 'card.permissive': '启用 Permissive 档位',\n 'card.permissiveHint': '关闭时门禁行为与此前完全一致。',\n 'card.strategies': '后台审批策略(至少一个生效;可组合)',\n 'card.strategy.trustAutoAllow': 'trustAutoAllow — 作用域内安全操作自动放行,危险/未知转 ask(中间档基线)',\n 'card.strategy.alwaysConfirm': 'alwaysConfirm — 一律逐次 ask;审批面板「允许控件」含两个扩展按钮:「本会话重复允许该类」(会话语限次 grant)与「允许所有类型」(命令词持久迁入 rulesFile 白名单)',\n 'card.strategy.llmAssist': 'llmAssist — 自定义 LLM 按风险分级裁决:safe 自动放行;deletion/credential/remote/system/bulk 硬风险永远转人工;neutral 进入确认学习;失败/超时回退人工',\n 'card.readonly': '只读',\n 'card.unavailable': '设置命名空间不可用:请确认 dsh-perm-gate 已装配进 profile。',\n 'card.llmReceiver': 'llmAssist 接收 LLM(OpenAI 兼容,可用自定义 API)',\n 'card.llmEndpoint': 'Endpoint(BaseURL,如 https://api.openai.com/v1)',\n 'card.llmModel': 'Model(模型 id,如 deepseek-chat)',\n 'card.llmKey': 'API Key(保密)',\n 'card.llmKeyHidden': '未设置 · 首次输入后以 * 显示',\n 'card.llmKeyMasked': '已设置 · 输入新值覆盖',\n 'card.llmKeyOverwrite': '已设置保密值,输入新值并失焦即可覆盖。',\n 'card.llmKeyClear': '清除',\n 'card.allowlist': '白名单(allow,rulesFile 同步)',\n 'card.allowlistHint': '一行一个命令模式,换行即一个元素;保存后写入 rulesFile 的 allow 段并重载(Ctrl/Cmd+Enter 保存)。',\n 'card.allowlistSave': '保存白名单',\n 'card.riskLearning': 'riskLearning — 裁决学习:neutral 风险操作经人工确认并执行后计数,满阈值且操作指纹一致时自动放行',\n 'card.riskLearningHint': '学习状态为插件自有数据($DSH_HOME/perm-gate/learning.json),不写入你的 YAML 规则文件;不同目标永不复用放行。默认关闭。',\n 'card.riskThreshold': '自动放行所需人工确认次数(1–10)',\n 'notice.label': '权限门禁动态',\n} satisfies Record<string, string>\n\n/** English dictionary (keys mirror zh). */\nexport const en: Record<keyof typeof zh, string> = {\n 'card.title': 'Permissive approval tier',\n 'card.description': 'Independent approval mode tier, parallel to read-only / full-access / whitelist. The front-end exposes a single switch; the three backend approval strategies are combinable via this panel and still fail-closed against P0 hard-deny.',\n 'card.permissive': 'Enable Permissive tier',\n 'card.permissiveHint': 'When off, the gate behaves exactly as before.',\n 'card.strategies': 'Backend approval strategies (at least one effective; combinable)',\n 'card.strategy.trustAutoAllow': 'trustAutoAllow — safe in-scope ops auto-allow; dangerous/unknown ask (baseline middle tier)',\n 'card.strategy.alwaysConfirm': 'alwaysConfirm — every crossing asks; the approval \"allow controls\" get two extended buttons: \"repeat-allow this type this session\" (bounded session grant) and \"allow every occurrence\" (persist the command word into the rulesFile whitelist)',\n 'card.strategy.llmAssist': 'llmAssist — a custom LLM grades risk: safe auto-allows; deletion/credential/remote/system/bulk hard risks always ask; neutral enters confirm-learning; failure/timeout falls back to the human',\n 'card.readonly': 'Read-only',\n 'card.unavailable': 'Settings namespace unavailable: make sure dsh-perm-gate is assembled into this profile.',\n 'card.llmReceiver': 'llmAssist receiving LLM (OpenAI-compatible; any custom API)',\n 'card.llmEndpoint': 'Endpoint (BaseURL, e.g. https://api.openai.com/v1)',\n 'card.llmModel': 'Model (model id, e.g. deepseek-chat)',\n 'card.llmKey': 'API Key (secret)',\n 'card.llmKeyHidden': 'Unset · shown as * after first entry',\n 'card.llmKeyMasked': 'Set · type a new value to overwrite',\n 'card.llmKeyOverwrite': 'A secret is stored; type a new value and blur to overwrite it.',\n 'card.llmKeyClear': 'Clear',\n 'card.allowlist': 'Whitelist (allow, synced to rulesFile)',\n 'card.allowlistHint': 'One command pattern per line (a newline is one element). Saving writes the rulesFile allow section and reloads (Ctrl/Cmd+Enter to save).',\n 'card.allowlistSave': 'Save whitelist',\n 'card.riskLearning': 'riskLearning — verdict learning: neutral-risk asks that the human approves and that execute count up; the same operation auto-allows at threshold with a matching fingerprint',\n 'card.riskLearningHint': 'Learning state is plugin-owned ($DSH_HOME/perm-gate/learning.json) and never written into your YAML rules; a different target never reuses authority. Off by default.',\n 'card.riskThreshold': 'Human confirmations required before auto-allow (1–10)',\n 'notice.label': 'Permission gate activity',\n}\n\n/** Japanese dictionary (keys mirror zh). */\nexport const ja: Record<keyof typeof zh, string> = {\n 'card.title': 'Permissive 承認ティア',\n 'card.description': 'read-only / full-access / whitelist と並ぶ独立の承認モード。フロントは単一スイッチのみ。バックエンドの 3 つの承認戦略はこのパネルで組み合わせ可能で、P0 ハード拒否に対して依然 fail-closed。',\n 'card.permissive': 'Permissive ティアを有効化',\n 'card.permissiveHint': 'オフのときは以前と完全に同じ動作です。',\n 'card.strategies': 'バックエンド承認戦略(少なくとも 1 つ有効、組み合わせ可)',\n 'card.strategy.trustAutoAllow': 'trustAutoAllow — スコープ内の安全操作は自動許可、危険/不明は ask(中間ティアのベースライン)',\n 'card.strategy.alwaysConfirm': 'alwaysConfirm — すべて ask。承認パネルの「許可コントロール」に2つの拡張ボタン:「このセッションで当該種別を繰り返し許可」(セッション限次 grant)と「すべての発生を許可」(コマンド語を rulesFile の許可リストへ永続化)',\n 'card.strategy.llmAssist': 'llmAssist — カスタム LLM がリスクを判定:safe は自動許可、deletion/credential/remote/system/bulk のハードリスクは常に人手、neutral は確認学習へ、失敗/タイムアウトは人手にフォールバック',\n 'card.readonly': '読み取り専用',\n 'card.unavailable': '設定名前空間が利用できません:dsh-perm-gate がこの profile に組み込まれているか確認してください。',\n 'card.llmReceiver': 'llmAssist 受信 LLM(OpenAI 互換、カスタム API 可)',\n 'card.llmEndpoint': 'Endpoint(BaseURL、例:https://api.openai.com/v1)',\n 'card.llmModel': 'Model(モデル id、例:deepseek-chat)',\n 'card.llmKey': 'API Key(機密)',\n 'card.llmKeyHidden': '未設定・初回入力後は * 表示',\n 'card.llmKeyMasked': '設定済み・新しい値を入力すると上書き',\n 'card.llmKeyOverwrite': '機密値が保存されています。新しい値を入力してフォーカスを外すと上書きします。',\n 'card.llmKeyClear': 'クリア',\n 'card.allowlist': '許可リスト(allow、rulesFile と同期)',\n 'card.allowlistHint': '1 行に 1 コマンドパターン(改行 = 1 要素)。保存で rulesFile の allow に書き込み再読込(Ctrl/Cmd+Enter で保存)。',\n 'card.allowlistSave': '許可リストを保存',\n 'card.riskLearning': 'riskLearning — 裁決学習:neutral リスク操作が人手で承認され実行されるとカウントし、しきい値到達後は同一操作(指紋一致)のみ自動許可',\n 'card.riskLearningHint': '学習状態はプラグイン所有($DSH_HOME/perm-gate/learning.json)で、YAML ルールには書き込みません。別の対象は決して権限を再利用しません。既定はオフ。',\n 'card.riskThreshold': '自動許可に必要な人手確認回数(1–10)',\n 'notice.label': '権限ゲートの活動',\n}\n\n/** Korean dictionary (keys mirror zh). */\nexport const ko: Record<keyof typeof zh, string> = {\n 'card.title': 'Permissive 승인 티어',\n 'card.description': 'read-only / full-access / whitelist와 나란한 독립 승인 모드. 프론트는 단일 스위치만 노출. 백엔드의 세 가지 승인 전략은 이 패널에서 조합 가능하며 P0 하드 거부에 대해 여전히 fail-closed.',\n 'card.permissive': 'Permissive 티어 활성화',\n 'card.permissiveHint': '꺼져 있으면 이전과 완전히 동일하게 동작합니다.',\n 'card.strategies': '백엔드 승인 전략(하나 이상 유효, 조합 가능)',\n 'card.strategy.trustAutoAllow': 'trustAutoAllow — 범위 내 안전 작업은 자동 허용, 위험/미확인은 ask(중간 티어 기준)',\n 'card.strategy.alwaysConfirm': 'alwaysConfirm — 모든 경계를 ask. 승인 패널의「허용 컨트롤」에 두 개의 확장 버튼:「이 세션에서 해당 유형 반복 허용」(세션 제한 grant) 과「모든 발생 허용」(명령어를 rulesFile 허용 목록에 영구 추가)',\n 'card.strategy.llmAssist': 'llmAssist — 사용자 지정 LLM이 위험을 판정: safe는 자동 허용, deletion/credential/remote/system/bulk 하드 위험은 항상 사람에게, neutral은 확인 학습으로, 실패/시간 초과는 사람에게 폴백',\n 'card.readonly': '읽기 전용',\n 'card.unavailable': '설정 네임스페이스를 사용할 수 없습니다: dsh-perm-gate가 이 profile에 조립되었는지 확인하세요.',\n 'card.llmReceiver': 'llmAssist 수신 LLM (OpenAI 호환, 사용자 지정 API 가능)',\n 'card.llmEndpoint': 'Endpoint (BaseURL, 예: https://api.openai.com/v1)',\n 'card.llmModel': 'Model (모델 id, 예: deepseek-chat)',\n 'card.llmKey': 'API Key (비밀)',\n 'card.llmKeyHidden': '미설정 · 최초 입력 후 * 표시',\n 'card.llmKeyMasked': '설정됨 · 새 값을 입력하면 덮어씀',\n 'card.llmKeyOverwrite': '비밀 값이 저장되어 있습니다. 새 값을 입력하고 포커스를 벗어나면 덮어씁니다.',\n 'card.llmKeyClear': '지우기',\n 'card.allowlist': '허용 목록(allow, rulesFile과 동기화)',\n 'card.allowlistHint': '한 줄에 명령 패턴 하나(줄바꿈 = 요소 하나). 저장하면 rulesFile의 allow에 기록 후 재로드(Ctrl/Cmd+Enter로 저장).',\n 'card.allowlistSave': '허용 목록 저장',\n 'card.riskLearning': 'riskLearning — 판정 학습: neutral 위험 요청이 사람의 승인과 실행까지 완료되면 카운트되며, 임계값 도달 후 동일 작업(지문 일치)만 자동 허용',\n 'card.riskLearningHint': '학습 상태는 플러그인 소유($DSH_HOME/perm-gate/learning.json)이며 YAML 규칙에는 기록하지 않습니다. 다른 대상은 권한을 재사용하지 않습니다. 기본값은 꺼짐.',\n 'card.riskThreshold': '자동 허용 전 필요한 사람 확인 횟수(1–10)',\n 'notice.label': '권한 게이트 활동',\n}","/**\n * Permissive settings card — the `settings.plugin.item` face of dsh-perm-gate.\n *\n * The card binds the `dsh-perm-gate` settings namespace through the\n * `settingsScope` cordis service and renders its fields: the single front switch\n * (`permissive`) plus the three combinable backend strategies\n * (`trustAutoAllow` / `alwaysConfirm` / `llmAssist`). Every change commits\n * immediately through the scope (no staged form); the host reads the namespace\n * live, so a committed change applies to the next tool call without a restart.\n *\n * Kept dependency-free beyond react: the scope is subscribed with\n * `useSyncExternalStore`, and the controls are plain HTML.\n */\nimport { useState, useSyncExternalStore } from 'react'\nimport type { CSSProperties, JSX } from 'react'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The settings namespace value the host registers (kept in lockstep with src/index.ts). */\nexport interface PermissiveCardValue {\n permissive?: boolean\n permissiveStrategies?: { trustAutoAllow?: boolean; alwaysConfirm?: boolean; llmAssist?: boolean }\n /** OpenAI-compatible endpoint the llmAssist classifier calls (custom API allowed). */\n classifierEndpoint?: string\n /** Model id used by the llmAssist classifier. */\n classifierModel?: string\n /** Secret bearer token / API key for the classifier endpoint (masked in the UI). */\n classifierApiKey?: string\n /** Timeout for one llmAssist risk call (ms). */\n riskTimeoutMs?: number\n /** Verdict learning: neutral-risk human confirmations may auto-allow the exact same operation later. */\n riskLearning?: boolean\n /** Confirmations required before a learned auto-allow (1–10). */\n riskThreshold?: number\n /** Editable whitelist (allow-list command patterns), one per entry. */\n allowlist?: string[]\n}\n\n/** One injected face: the plugin's own settings scope. */\nexport interface PermissiveCardInjected {\n scope: SettingsScope<PermissiveCardValue>\n}\n\n/** Full props: locale seat + the injected scope. */\nexport type PermissiveCardProps = PropsLocale<'dsh-perm-gate'> & PermissiveCardInjected\n\nconst rowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: '12px',\n padding: '6px 0',\n fontSize: '13px',\n lineHeight: '20px',\n}\n\nconst labelStyle: CSSProperties = { margin: 0, color: 'var(--dsw-alias-label-primary)' }\n\nconst hintStyle: CSSProperties = { margin: '4px 0 0', fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' }\n\nconst sectionStyle: CSSProperties = {\n marginTop: '12px',\n paddingTop: '10px',\n borderTop: '1px solid var(--dsw-alias-border-l2)',\n}\n\nconst controlStyle: CSSProperties = {\n background: 'var(--dsw-alias-bg-surface, #fff)',\n color: 'var(--dsw-alias-label-primary)',\n border: '1px solid var(--dsw-alias-border-l2)',\n borderRadius: '4px',\n padding: '3px 8px',\n fontSize: '13px',\n}\n\nconst fieldStyle: CSSProperties = { display: 'flex', flexDirection: 'column', gap: '4px' }\n\nconst fieldLabelStyle: CSSProperties = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' }\n\n/**\n * The card body, wrapped in a disclosure shell like every peer settings card:\n * a header (name + description + chevron) toggling the body, collapsed by\n * default so the plugin tab stays a tidy list of drawers.\n */\nexport function PermissiveCard({ t, scope }: PermissiveCardProps): JSX.Element {\n const [open, setOpen] = useState(false)\n // The api key is kept OUT of the visible value (secret): a fresh typed draft\n // overwrites on blur; an already-set key shows only a masked placeholder.\n const [apiKeyDraft, setApiKeyDraft] = useState('')\n // Whitelist draft (one pattern per line); `allowDirty` avoids clobbering user\n // edits when the host re-seeds the namespace after mount.\n const [allowText, setAllowText] = useState('')\n const [allowDirty, setAllowDirty] = useState(false)\n const snapshot = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n )\n const unavailable = snapshot.status === 'unavailable'\n const readonly = unavailable || !snapshot.writable\n const value = (snapshot.value ?? {}) as Partial<PermissiveCardValue>\n const strategies = value.permissiveStrategies ?? {}\n const hasApiKey = typeof value.classifierApiKey === 'string' && value.classifierApiKey !== ''\n\n // The backend strategy toggles only take effect while the tier is on.\n const effective = value.permissive === true\n\n const commitApiKey = (): void => {\n const next = apiKeyDraft.trim()\n if (next === '') return\n void scope.set('classifierApiKey', next)\n setApiKeyDraft('') // never echo the secret back after it is stored\n }\n const clearApiKey = (): void => {\n void scope.unset('classifierApiKey')\n setApiKeyDraft('')\n }\n\n // Show the persisted allowlist until the user starts editing a fresh draft.\n const allowlistShown = allowDirty ? allowText : (value.allowlist ?? []).join('\\n')\n const commitAllowlist = (): void => {\n const lines = allowlistShown.split('\\n').map((s) => s.trim()).filter((s) => s.length > 0)\n void scope.set('allowlist', lines)\n setAllowDirty(false)\n }\n\n return (\n <div style={{\n border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))',\n background: 'var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.05))',\n borderRadius: '12px',\n transition: 'border-color 0.16s, background 0.16s',\n }}>\n <button\n type=\"button\"\n aria-expanded={open}\n style={{\n appearance: 'none',\n width: '100%',\n font: 'inherit',\n color: 'inherit',\n textAlign: 'left',\n cursor: 'pointer',\n background: 'none',\n border: 0,\n borderRadius: '12px',\n display: 'flex',\n alignItems: 'center',\n gap: '12px',\n padding: '14px 16px',\n }}\n onClick={() => { setOpen(current => !current) }}\n >\n <span style={{ flex: '1 1 0%', minWidth: 0 }}>\n <div style={{ fontSize: '14px', fontWeight: 600, color: 'var(--dsw-alias-label-primary)' }}>{t('card.title')}</div>\n <div style={{ color: 'var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))', fontSize: '13px', lineHeight: 1.5 }}>{t('card.description')}</div>\n </span>\n <svg\n width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden\n style={{\n color: 'var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))',\n flex: '0 0 auto',\n transition: 'transform 0.16s',\n transform: open ? 'rotate(180deg)' : 'none',\n }}\n >\n <path d=\"M4 6l4 4 4-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </button>\n {open\n ? (\n <div style={{ padding: '12px 16px' }}>\n {unavailable\n ? (\n <div style={{ fontSize: '13px', color: 'var(--dsw-alias-label-tertiary)' }}>\n {t('card.unavailable')}\n </div>\n )\n : (\n <>\n <div style={rowStyle}>\n <label htmlFor=\"plugin-config-perm-gate-permissive\" style={labelStyle}>{t('card.permissive')}</label>\n <input\n id=\"plugin-config-perm-gate-permissive\"\n type=\"checkbox\"\n checked={effective}\n disabled={readonly}\n onChange={(event) => { void scope.set('permissive', event.currentTarget.checked) }}\n />\n </div>\n <p style={hintStyle}>{t('card.permissiveHint')}</p>\n\n <section style={sectionStyle}>\n <p style={labelStyle}>{t('card.strategies')}</p>\n <div style={rowStyle}>\n <label htmlFor=\"plugin-config-perm-gate-trust\" style={{ fontSize: '12px' }}>{t('card.strategy.trustAutoAllow')}</label>\n <input\n id=\"plugin-config-perm-gate-trust\"\n type=\"checkbox\"\n checked={strategies.trustAutoAllow ?? true}\n disabled={readonly || !effective}\n onChange={(event) => {\n void scope.set('permissiveStrategies', {\n ...strategies,\n trustAutoAllow: event.currentTarget.checked,\n })\n }}\n />\n </div>\n <div style={rowStyle}>\n <label htmlFor=\"plugin-config-perm-gate-confirm\" style={{ fontSize: '12px' }}>{t('card.strategy.alwaysConfirm')}</label>\n <input\n id=\"plugin-config-perm-gate-confirm\"\n type=\"checkbox\"\n checked={strategies.alwaysConfirm ?? false}\n disabled={readonly || !effective}\n onChange={(event) => {\n void scope.set('permissiveStrategies', {\n ...strategies,\n alwaysConfirm: event.currentTarget.checked,\n })\n }}\n />\n </div>\n <div style={rowStyle}>\n <label htmlFor=\"plugin-config-perm-gate-llm\" style={{ fontSize: '12px' }}>{t('card.strategy.llmAssist')}</label>\n <input\n id=\"plugin-config-perm-gate-llm\"\n type=\"checkbox\"\n checked={strategies.llmAssist ?? false}\n disabled={readonly || !effective}\n onChange={(event) => {\n void scope.set('permissiveStrategies', {\n ...strategies,\n llmAssist: event.currentTarget.checked,\n })\n }}\n />\n </div>\n {strategies.llmAssist === true\n ? (\n <section style={sectionStyle}>\n <p style={labelStyle}>{t('card.llmReceiver')}</p>\n <label style={fieldStyle}>\n <span style={fieldLabelStyle}>{t('card.llmEndpoint')}</span>\n <input\n type=\"text\"\n value={value.classifierEndpoint ?? ''}\n disabled={readonly}\n placeholder=\"https://api.openai.com/v1\"\n style={controlStyle}\n onChange={(event) => { void scope.set('classifierEndpoint', event.currentTarget.value) }}\n onBlur={(event) => {\n if (event.currentTarget.value.trim() === '') void scope.unset('classifierEndpoint')\n }}\n />\n </label>\n <label style={fieldStyle}>\n <span style={fieldLabelStyle}>{t('card.llmModel')}</span>\n <input\n type=\"text\"\n value={value.classifierModel ?? ''}\n disabled={readonly}\n placeholder=\"deepseek-chat\"\n style={controlStyle}\n onChange={(event) => { void scope.set('classifierModel', event.currentTarget.value) }}\n onBlur={(event) => {\n if (event.currentTarget.value.trim() === '') void scope.unset('classifierModel')\n }}\n />\n </label>\n <label style={fieldStyle}>\n <span style={fieldLabelStyle}>{t('card.llmKey')}</span>\n <div style={{ display: 'flex', gap: '6px' }}>\n <input\n type=\"password\"\n autoComplete=\"off\"\n spellCheck={false}\n value={apiKeyDraft}\n disabled={readonly}\n placeholder={hasApiKey ? t('card.llmKeyMasked') : t('card.llmKeyHidden')}\n style={{ ...controlStyle, flex: '1 1 0%' }}\n onChange={(event) => setApiKeyDraft(event.currentTarget.value)}\n onBlur={commitApiKey}\n onKeyDown={(event) => {\n if (event.key === 'Enter') (event.currentTarget as HTMLInputElement).blur()\n }}\n />\n {hasApiKey\n ? (\n <button type=\"button\" disabled={readonly} onClick={clearApiKey} style={controlStyle}>\n {t('card.llmKeyClear')}\n </button>\n )\n : null}\n </div>\n {hasApiKey && <span style={hintStyle}>{t('card.llmKeyOverwrite')}</span>}\n </label>\n <div style={rowStyle}>\n <label htmlFor=\"plugin-config-perm-gate-risk-learning\" style={{ fontSize: '12px' }}>{t('card.riskLearning')}</label>\n <input\n id=\"plugin-config-perm-gate-risk-learning\"\n type=\"checkbox\"\n checked={value.riskLearning ?? false}\n disabled={readonly || !effective}\n onChange={(event) => { void scope.set('riskLearning', event.currentTarget.checked) }}\n />\n </div>\n <p style={hintStyle}>{t('card.riskLearningHint')}</p>\n {value.riskLearning === true\n ? (\n <label style={fieldStyle}>\n <span style={fieldLabelStyle}>{t('card.riskThreshold')}</span>\n <input\n id=\"plugin-config-perm-gate-risk-threshold\"\n type=\"number\"\n min={1}\n max={10}\n value={value.riskThreshold ?? 3}\n disabled={readonly || !effective}\n style={{ ...controlStyle, width: '96px' }}\n onChange={(event) => {\n const n = Number.parseInt(event.currentTarget.value, 10)\n if (Number.isFinite(n) && n >= 1 && n <= 10) void scope.set('riskThreshold', n)\n }}\n />\n </label>\n )\n : null}\n </section>\n )\n : null}\n </section>\n\n <section style={sectionStyle}>\n <p style={labelStyle}>{t('card.allowlist')}</p>\n <p style={hintStyle}>{t('card.allowlistHint')}</p>\n <textarea\n id=\"plugin-config-perm-gate-allowlist\"\n value={allowlistShown}\n disabled={readonly}\n rows={6}\n spellCheck={false}\n style={{\n ...controlStyle,\n width: '100%',\n boxSizing: 'border-box',\n resize: 'vertical',\n fontFamily: 'var(--ds-font-family-code, monospace)',\n whiteSpace: 'pre',\n }}\n onChange={(event) => {\n setAllowDirty(true)\n setAllowText(event.currentTarget.value)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) commitAllowlist()\n }}\n />\n <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '6px' }}>\n <button\n type=\"button\"\n disabled={readonly || !allowDirty}\n onClick={commitAllowlist}\n style={{\n ...controlStyle,\n cursor: readonly || !allowDirty ? 'default' : 'pointer',\n opacity: readonly || !allowDirty ? 0.5 : 1,\n }}\n >\n {t('card.allowlistSave')}\n </button>\n </div>\n </section>\n {!snapshot.writable\n && <p style={{ margin: '8px 0 0', fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }}>{t('card.readonly')}</p>}\n </>\n )}\n </div>\n )\n : null}\n </div>\n )\n}","/**\n * Permission-picker icon decoration for the Permissive tier.\n *\n * dsh-auto-mode injects a shield glyph onto its \"Auto\" permission menu item\n * via a `data-*` attribute + a CSS mask. This module does the same for the\n * \"Permissive\" preset so the two approval tiers look consistent in the\n * dropdown. It only annotates the item; the preset itself comes from\n * `cordis.patch.yml` (the `permission.config.presets` table).\n *\n * Pure DOM + CSS, no @deepseek-ai value imports (client bundle purity).\n */\nconst PLUGIN_ID = 'dsh-perm-gate'\nconst ICON_ATTRIBUTE = 'data-dsh-perm-gate-icon'\n\n/** Labels of the Permissive preset item, current + future localized variants. */\nconst PERMISSIVE_LABELS = new Set(['Permissive', 'Permissive 审批档', 'Permissive 审批档位'])\n\n/** Same shield glyph as dsh-auto-mode so the two approval tiers match visually. */\nconst ICON_SVG = '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path d=\"M8.21.9l6.58 2.47v3.64c0 4.99-3.74 7.2-6.58 8.29C5.36 14.21 1.62 12 1.62 7.01V3.37L8.21.9Z\" fill=\"none\" stroke=\"black\" stroke-width=\"1.32\" stroke-linejoin=\"round\"/><path d=\"M8.75 3.65 5.95 8.2h2.08l-.78 4.15 2.82-4.9H8.12l.63-3.8Z\" fill=\"black\"/></svg>'\n\nfunction iconStyles(): string {\n const mask = `url(\"data:image/svg+xml,${encodeURIComponent(ICON_SVG)}\")`\n return `\n[${ICON_ATTRIBUTE}]::before {\n content: \"\";\n display: inline-block;\n flex: 0 0 auto;\n width: 16px;\n height: 16px;\n background-color: var(--dsw-alias-label-tertiary, currentColor);\n -webkit-mask-image: ${mask};\n mask-image: ${mask};\n -webkit-mask-position: center;\n mask-position: center;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-size: contain;\n}\n[${ICON_ATTRIBUTE}='trigger']::before {\n width: 14px;\n height: 14px;\n margin-right: 6px;\n}\n`\n}\n\n/** The permission dropdown menu (lists the permission presets as menuitems). */\nfunction isPermissionMenu(menu: Element): boolean {\n return menu.matches('[role=\"menu\"]')\n && Array.from(menu.querySelectorAll('button[role=\"menuitem\"]')).length >= 4\n}\n\n/** Whether one menuitem is the Permissive preset. */\nfunction isPermissiveMenuItem(element: Element): boolean {\n if (!element.matches('button[role=\"menuitem\"]')) return false\n const label = (element.textContent ?? '').replace(/\\s+/g, ' ').trim()\n if (!PERMISSIVE_LABELS.has(label)) return false\n return element.closest('[role=\"menu\"]') !== null\n}\n\n/** The collapsed permission-picker trigger (opens the presets menu). */\nfunction isPermissionTrigger(element: Element): boolean {\n return element.matches('button[aria-haspopup=\"menu\"]')\n && PERMISSIVE_LABELS.has((element.textContent ?? '').replace(/\\s+/g, ' ').trim())\n}\n\n/**\n * Annotate the Permissive permission menuitems with the icon attribute, plus\n * the collapsed picker trigger when its label names the Permissive preset.\n * Unknown DOM shapes simply never match, so decoration stays conservative.\n */\nfunction decorate(document: Document): void {\n for (const marked of document.querySelectorAll(`[${ICON_ATTRIBUTE}]`)) {\n if (!isPermissiveMenuItem(marked) && !isPermissionTrigger(marked)) marked.removeAttribute(ICON_ATTRIBUTE)\n }\n for (const menu of document.querySelectorAll('[role=\"menu\"]')) {\n if (!isPermissionMenu(menu)) continue\n for (const item of menu.querySelectorAll('button[role=\"menuitem\"]')) {\n if (isPermissiveMenuItem(item)) item.setAttribute(ICON_ATTRIBUTE, 'menu')\n }\n }\n for (const trigger of document.querySelectorAll('button[aria-haspopup=\"menu\"]')) {\n if (isPermissionTrigger(trigger)) trigger.setAttribute(ICON_ATTRIBUTE, 'trigger')\n }\n}\n\n/** Install the Permissive tile icon into the permission picker; returns the disposer. */\nexport function installPermissivePermissionIcon(document: Document): () => void {\n for (const existing of document.querySelectorAll('style[data-plugin]')) {\n if (existing.getAttribute('data-plugin') === PLUGIN_ID) existing.remove()\n }\n const style = document.createElement('style')\n style.dataset.plugin = PLUGIN_ID\n style.dataset.pluginCss = `${PLUGIN_ID}/permission-icon`\n style.textContent = iconStyles()\n document.head.appendChild(style)\n\n let active = true\n let queued = false\n const scan = (): void => {\n if (!active || queued) return\n queued = true\n queueMicrotask(() => {\n queued = false\n if (active) decorate(document)\n })\n }\n\n decorate(document)\n const observer = new MutationObserver(scan)\n observer.observe(document.documentElement, { childList: true, characterData: true, subtree: true })\n\n return () => {\n active = false\n observer.disconnect()\n for (const marked of document.querySelectorAll(`[${ICON_ATTRIBUTE}]`)) {\n marked.removeAttribute(ICON_ATTRIBUTE)\n }\n style.remove()\n }\n}","/**\n * Gate decision notice strip — the `conversation.input.dock` face of\n * dsh-perm-gate (a simplified version of the review-feed pattern demonstrated\n * by dsh-approval-gate).\n *\n * Polls the host's `GET /api/dsh-perm-gate/events?sessionId=&since=` feed every\n * 2 s and shows the latest decision above the conversation input: auto-allows\n * (green) and denies (red) auto-dismiss after a few seconds; an ask (amber)\n * stays until the next event because it needs the human's attention.\n *\n * Everything is best-effort: no session id, a missing route, or any fetch\n * failure simply means \"render nothing\". No @deepseek-ai value imports.\n */\nimport { useEffect, useRef, useState } from 'react'\nimport type { JSX } from 'react'\n\n/** One event as delivered by the host route (see src/events.ts). */\ninterface GateEvent {\n readonly id: number\n readonly ts: string\n readonly sessionId: string\n readonly tool: string\n readonly kind: 'auto' | 'ask' | 'deny' | 'learned'\n readonly risk?: string\n readonly reason: string\n}\n\n/** Slots props carrying the current conversation id (best effort). */\ninterface DockSlotsProps {\n readonly sessionId?: unknown\n readonly useSessions?: (selector: (state: { current?: unknown }) => { current?: unknown }) => { current?: unknown }\n}\n\nconst POLL_MS = 2_000\nconst AUTO_HIDE_MS = 4_000\n\nasync function fetchEvents(sessionId: string, since: number): Promise<GateEvent[]> {\n const query = `/api/dsh-perm-gate/events?sessionId=${encodeURIComponent(sessionId)}${since > 0 ? `&since=${since}` : ''}`\n const res = await fetch(query, { headers: { 'cache-control': 'no-cache' } })\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const body = (await res.json()) as { events?: unknown }\n return Array.isArray(body.events) ? (body.events as GateEvent[]) : []\n}\n\n/** Resolve the current session id from the slot props; null when unavailable. */\nfunction resolveSessionId(props: DockSlotsProps | undefined): string | null {\n const direct = props?.sessionId\n if (typeof direct === 'string' && direct !== '') return direct\n try {\n if (typeof props?.useSessions === 'function') {\n const state = props.useSessions((s) => s)\n if (typeof state?.current === 'string' && state.current !== '') return state.current\n }\n } catch {\n // fall through\n }\n return null\n}\n\n/** Presentation per event kind: accent color, tag, and whether it auto-dismisses. */\nfunction presentation(kind: GateEvent['kind']): { color: string; bg: string; tag: string; sticky: boolean } {\n switch (kind) {\n case 'deny':\n return { color: 'var(--dsw-alias-state-error-primary, #c0392b)', bg: 'var(--dsw-alias-interactive-bg-hover-danger, rgba(192,57,43,0.08))', tag: 'DENY', sticky: false }\n case 'ask':\n return { color: 'var(--dsw-alias-state-warn-label, #b9770e)', bg: 'var(--dsw-alias-state-warn-tertiary, rgba(185,119,14,0.08))', tag: 'ASK', sticky: true }\n case 'learned':\n return { color: 'var(--dsw-alias-state-success-primary, #1e8449)', bg: 'var(--dsw-alias-state-success-tertiary, rgba(30,132,73,0.08))', tag: 'LEARNED', sticky: false }\n default:\n return { color: 'var(--dsw-alias-state-success-primary, #1e8449)', bg: 'var(--dsw-alias-state-success-tertiary, rgba(30,132,73,0.08))', tag: 'ALLOW', sticky: false }\n }\n}\n\nexport function NoticeStrip({ slotsProps }: { slotsProps?: DockSlotsProps }): JSX.Element | null {\n const sessionId = resolveSessionId(slotsProps)\n const [notice, setNotice] = useState<GateEvent | null>(null)\n const sinceRef = useRef(0)\n const shownIdRef = useRef(0)\n const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useEffect(() => {\n sinceRef.current = 0\n shownIdRef.current = 0\n setNotice(null)\n if (sessionId === null) return\n\n let alive = true\n let timer: ReturnType<typeof setInterval> | null = null\n\n const startPolling = (): void => {\n if (!alive) return\n timer = setInterval(() => {\n fetchEvents(sessionId, sinceRef.current)\n .then((events) => {\n if (!alive || events.length === 0) return\n const last = events[events.length - 1]\n if (last === undefined || last.id <= shownIdRef.current) return\n shownIdRef.current = last.id\n sinceRef.current = last.id\n setNotice(last)\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current)\n if (!presentation(last.kind).sticky) {\n hideTimerRef.current = setTimeout(() => { setNotice(null) }, AUTO_HIDE_MS)\n }\n })\n .catch(() => {}) // route missing / transient failure: keep quiet\n }, POLL_MS)\n }\n\n // On session open, advance the cursor silently past history (no replay popups).\n fetchEvents(sessionId, 0)\n .then((events) => {\n if (!alive) return\n if (events.length > 0) {\n sinceRef.current = events[events.length - 1]?.id ?? 0\n shownIdRef.current = sinceRef.current\n }\n startPolling()\n })\n .catch(startPolling)\n\n return () => {\n alive = false\n if (timer !== null) clearInterval(timer)\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current)\n }\n }, [sessionId])\n\n if (notice === null) return null\n const style = presentation(notice.kind)\n return (\n <div\n role=\"status\"\n style={{\n boxSizing: 'border-box',\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n margin: '0 auto 6px',\n maxWidth: '720px',\n border: `1px solid ${style.color}`,\n background: style.bg,\n borderRadius: '10px',\n padding: '5px 10px',\n }}\n >\n <span\n style={{\n flex: '0 0 auto',\n fontSize: '11px',\n lineHeight: '18px',\n padding: '0 8px',\n borderRadius: '9px',\n color: style.color,\n fontWeight: 600,\n }}\n >\n {style.tag}\n </span>\n <span\n style={{\n flex: '1 1 auto',\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n fontSize: '12px',\n color: 'var(--dsw-alias-label-secondary, inherit)',\n }}\n >\n <code style={{ font: '500 12px/18px var(--ds-font-family-code, monospace)', color: 'var(--dsw-alias-label-primary, inherit)' }}>{notice.tool}</code>\n {' — '}\n {notice.reason}\n </span>\n <button\n type=\"button\"\n aria-label=\"dismiss\"\n onClick={() => { setNotice(null) }}\n style={{\n flex: '0 0 auto',\n width: '22px',\n height: '22px',\n border: 'none',\n borderRadius: '999px',\n background: 'transparent',\n color: 'var(--dsw-alias-label-tertiary, inherit)',\n cursor: 'pointer',\n }}\n >\n ×\n </button>\n </div>\n )\n}\n","/**\n * dsh-perm-gate — browser half.\n *\n * Registers the `dsh-perm-gate` dictionaries and one `settings.plugins.tab`\n * page keyed by the plugin's settings namespace, so the Plugins section of the\n * settings panel renders an editable page: the single Permissive tier switch\n * plus the three combinable backend approval strategies.\n *\n * All @deepseek-ai/* imports are type-only at the value level: collaboration\n * happens through cordis services (`slots`, `locale`, `settingsScope`) and slot\n * registration only (client bundle purity). The `LocaleNamespaceMap`\n * augmentation below is a type-only merge so the `locale:` seat type-checks.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { NS, en, zh, type PermissiveKey } from './locales.ts'\nimport { PermissiveCard, type PermissiveCardInjected, type PermissiveCardValue } from './card.tsx'\nimport { installPermissivePermissionIcon } from './permission-icon.ts'\nimport { NoticeStrip } from './notice.tsx'\n\n/** The settings namespace the host half registers (kept in lockstep with src/index.ts). */\nconst PERMISSIVE_NS = 'dsh-perm-gate'\n\n// Declare the plugin's dictionary namespace in the locale key domain so the\n// slot `locale:` seat and `ctx.locale.bind` are typed to our key union.\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n 'dsh-perm-gate': PermissiveKey\n }\n // The conversation dock slot is declared at runtime by the conversation UI\n // package (not a dev dependency here); mirror its contract so the typed\n // `slots.register` accepts our notice-strip entry. Approval-gate's client\n // demonstrates the runtime accepts this slot name with id/order/label options.\n interface SlotMap {\n 'conversation.input.dock': {\n kind: 'list'\n scope: 'session-maybe'\n owner: {\n /** Current conversation id (may be absent outside a session). */\n sessionId?: string\n /** Session-store selector hook supplied by the conversation shell. */\n useSessions?: (selector: (state: unknown) => unknown) => unknown\n }\n }\n }\n}\n\n/** Services required by the browser half. */\nexport const inject = ['slots', 'locale', 'settingsScope']\n\n/**\n * Client plugin body: dictionaries plus the settings page registration.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-perm-gate: dictionaries')\n\n // Decorate the Permissive permission-picker item with a shield glyph, matching\n // the Auto tier's icon. Guarded in case `document` is unavailable in a\n // non-browser build (tsdown targets the browser; the guard keeps typecheck green).\n ctx.effect(() => installPermissivePermissionIcon(globalThis.document), 'dsh-perm-gate: permission icon')\n\n // Decision notice strip above the conversation input: polls the host's\n // event feed so auto-allows / asks / denies are visible without opening logs.\n ctx.slots.inject('conversation.input.dock', function* () {\n yield ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'dsh-perm-gate.notice',\n order: 30,\n label: () => t('notice.label'),\n locale: NS,\n }, NoticeStrip)\n }) as unknown\n\n // A localized tab label (re-evaluated per read so it follows the active locale).\n const t = ctx.locale.bind(NS)\n\n ctx.slots.inject('settings.plugins.tab', function* () {\n yield ctx.slots.register({\n name: 'settings.plugins.tab',\n // List-slot cell identity + nav position + localized tab text.\n id: PERMISSIVE_NS,\n order: 50,\n label: () => t('card.title'),\n locale: NS,\n inject: (): PermissiveCardInjected => {\n const scope = ctx.settingsScope.bind<PermissiveCardValue>({ namespace: PERMISSIVE_NS })\n return { scope }\n },\n }, PermissiveCard)\n }) as unknown\n}"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /** A checkable pattern already compiled to a RegExp. */
2
+ export interface CompiledPattern {
3
+ readonly source: string;
4
+ readonly re: RegExp;
5
+ }
6
+ /** Raised at load time when a pattern cannot be compiled safely. */
7
+ export declare class PatternError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /** No-reconstruction bound: at most one unbounded `*` run per pattern. */
11
+ export declare const DEFAULT_MAX_STARS = 2;
12
+ /**
13
+ * Compile a simple glob into an anchored RegExp.
14
+ *
15
+ * - `*` matches any characters; when `segments` is true it stops at a path
16
+ * separator `/`.
17
+ * - `?` matches one char (not a separator when `segments`).
18
+ * - `[...]` char classes are preserved.
19
+ * - Consecutive unbounded `*` are collapsed into one; a pattern whose star-run
20
+ * count exceeds `maxStars` is rejected (ReDoS degree bound), never silently
21
+ * bounded to a partial match.
22
+ */
23
+ export declare function compileGlob(pattern: string, options?: {
24
+ segments?: boolean;
25
+ maxStars?: number;
26
+ }): CompiledPattern;
27
+ /** Compile a literal (fully escaped), never a glob. */
28
+ export declare function compileLiteral(pattern: string): CompiledPattern;
29
+ /** Stable SHA-256 content hash (used for rule-file compile caching). */
30
+ export declare function hashText(text: string): string;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Pattern compilation for dsh-perm-gate.
3
+ *
4
+ * Turns glob (and literal) patterns into anchored RegExps with a hard bound
5
+ * on the number of unbounded `*` quantifiers (ReDoS degree). Every function
6
+ * here is pure — no filesystem/clock/process state — so the compiler and its
7
+ * failure modes are unit-testable and replayable.
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ /** Raised at load time when a pattern cannot be compiled safely. */
11
+ export class PatternError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = 'PatternError';
15
+ }
16
+ }
17
+ /** No-reconstruction bound: at most one unbounded `*` run per pattern. */
18
+ export const DEFAULT_MAX_STARS = 2;
19
+ /**
20
+ * Compile a simple glob into an anchored RegExp.
21
+ *
22
+ * - `*` matches any characters; when `segments` is true it stops at a path
23
+ * separator `/`.
24
+ * - `?` matches one char (not a separator when `segments`).
25
+ * - `[...]` char classes are preserved.
26
+ * - Consecutive unbounded `*` are collapsed into one; a pattern whose star-run
27
+ * count exceeds `maxStars` is rejected (ReDoS degree bound), never silently
28
+ * bounded to a partial match.
29
+ */
30
+ export function compileGlob(pattern, options = {}) {
31
+ const { segments = false, maxStars = DEFAULT_MAX_STARS } = options;
32
+ if (pattern === '')
33
+ return { source: pattern, re: /^$/u };
34
+ // Collapse: keep each star (so run length survives), counting star-RUNS and
35
+ // rejecting patterns whose unbounded-run count exceeds maxStars.
36
+ const tokens = [];
37
+ let starRuns = 0;
38
+ let inClass = false;
39
+ for (let i = 0; i < pattern.length; i += 1) {
40
+ const ch = pattern[i];
41
+ if (ch === '[' && !inClass) {
42
+ inClass = true;
43
+ tokens.push(ch);
44
+ continue;
45
+ }
46
+ if (inClass) {
47
+ tokens.push(ch);
48
+ if (ch === ']')
49
+ inClass = false;
50
+ continue;
51
+ }
52
+ if (ch === '*') {
53
+ if (tokens.length === 0 || tokens[tokens.length - 1] !== '*') {
54
+ starRuns += 1;
55
+ if (starRuns > maxStars) {
56
+ throw new PatternError(`pattern "${pattern}" exceeds maxStars=${maxStars} unbounded glob stars`);
57
+ }
58
+ }
59
+ tokens.push('*');
60
+ }
61
+ else {
62
+ tokens.push(ch);
63
+ }
64
+ }
65
+ let out = '';
66
+ let i = 0;
67
+ const n = tokens.length;
68
+ while (i < n) {
69
+ const ch = tokens[i];
70
+ if (ch === '*') {
71
+ let run = 1;
72
+ while (tokens[i + run] === '*')
73
+ run += 1;
74
+ if (segments)
75
+ out += run >= 2 ? '.*' : '[^/]*';
76
+ else
77
+ out += '.*';
78
+ i += run;
79
+ }
80
+ else if (ch === '?') {
81
+ out += segments ? '[^/]' : '.';
82
+ i += 1;
83
+ }
84
+ else if (ch === '[') {
85
+ let j = i + 1;
86
+ let body = '';
87
+ if (tokens[j] === '!' || tokens[j] === '^') {
88
+ body += '^';
89
+ j += 1;
90
+ }
91
+ let closed = false;
92
+ while (j < n) {
93
+ const c = tokens[j];
94
+ if (c === ']') {
95
+ closed = true;
96
+ j += 1;
97
+ break;
98
+ }
99
+ body += c === '\\' ? '\\\\' : c;
100
+ j += 1;
101
+ }
102
+ if (!closed)
103
+ throw new PatternError(`pattern "${pattern}" has an unterminated character class`);
104
+ out += `[${body}]`;
105
+ i = j;
106
+ }
107
+ else {
108
+ out += escapeRegexChar(ch);
109
+ i += 1;
110
+ }
111
+ }
112
+ return { source: pattern, re: new RegExp(`^${out}$`, 'u') };
113
+ }
114
+ /** Compile a literal (fully escaped), never a glob. */
115
+ export function compileLiteral(pattern) {
116
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
117
+ return { source: pattern, re: new RegExp(`^${escaped}$`, 'u') };
118
+ }
119
+ /** Escape one character for safe inclusion in a RegExp literal. */
120
+ function escapeRegexChar(ch) {
121
+ return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
122
+ }
123
+ /** Stable SHA-256 content hash (used for rule-file compile caching). */
124
+ export function hashText(text) {
125
+ return createHash('sha256').update(text, 'utf8').digest('hex');
126
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Plugin configuration for dsh-perm-gate, defined with Schemastery so the DSH
3
+ * loader validates and fills defaults before `apply`. Invalid values fail loud.
4
+ */
5
+ import z from '@deepseek-ai/schemastery';
6
+ import type { RuleAction } from './rule.js';
7
+ export interface PermGateConfig {
8
+ /** Absolute or `./`-relative path to the YAML permissions document. */
9
+ readonly rulesFile?: string;
10
+ /** Absolute `$DSH_HOME` root (used for protected-target checks and root pinning). */
11
+ readonly dshHome?: string;
12
+ /** Fallback action when no rule matches. Default `ask`. */
13
+ readonly defaultAction?: RuleAction;
14
+ /** Compare path patterns case-insensitively (Windows default true). */
15
+ readonly caseInsensitivePaths?: boolean;
16
+ /** Enable the optional LLM semantic classifier (P3). Default false. */
17
+ readonly classifierEnabled?: boolean;
18
+ readonly classifierEndpoint?: string;
19
+ readonly classifierModel?: string;
20
+ readonly classifierApiKey?: string;
21
+ /** Abort the llmAssist risk call after this many milliseconds. Default 20000. */
22
+ readonly riskTimeoutMs?: number;
23
+ /**
24
+ * Verdict learning (llmAssist neutral-risk confirmations). When enabled,
25
+ * neutral-risk asks that the human approves and that actually execute count
26
+ * toward auto-allowing the exact same operation later. Default false.
27
+ */
28
+ readonly riskLearning?: boolean;
29
+ /** Confirmations required before a neutral-risk re-run may auto-allow. Default 3. */
30
+ readonly riskThreshold?: number;
31
+ /** Persistence path for verdict learning; defaults to `<dshHome>/perm-gate/learning.json`. */
32
+ readonly learningFile?: string;
33
+ /** Persistence path for the decision-event feed; defaults to `<dshHome>/perm-gate/events.jsonl`. */
34
+ readonly eventsFile?: string;
35
+ readonly grantTtlMs?: number;
36
+ readonly grantMaxUses?: number;
37
+ /**
38
+ * Permissive — an independent permission tier (a single front-facing switch).
39
+ * When on, the gate routes crossings through the backend `permissiveStrategies`
40
+ * below instead of the baseline P2/P4 path. It is NOT the Auto "auto-approval"
41
+ * mode: it never mints blanket authority, stays fail-closed, and only ever
42
+ * widens decision before the human/LLM seam.
43
+ */
44
+ readonly permissive?: boolean;
45
+ /** Backend approval strategies; combinable. Front-end exposes only `permissive` itself. */
46
+ readonly permissiveStrategies?: Partial<PermissiveStrategies>;
47
+ /**
48
+ * Editable whitelist (allow-list command patterns), mirrored to the rules
49
+ * file's `allow` section. Optional; edit from the settings card as a list.
50
+ */
51
+ readonly allowlist?: string[];
52
+ }
53
+ /** Backend combinable approval strategies for the Permissive tier (all opt-in). */
54
+ export interface PermissiveStrategies {
55
+ /** Trust-in-scope safe ops auto-allow; dangerous/in-scope-unknown ops ask. Baseline middle tier. */
56
+ readonly trustAutoAllow: boolean;
57
+ /** Every crossing asks (no automatic allow outside a session grant / hard-deny context). */
58
+ readonly alwaysConfirm: boolean;
59
+ /** LLM-assist classify first, human fallback on uncertainty or classifier failure. */
60
+ readonly llmAssist: boolean;
61
+ }
62
+ export declare const DEFAULT_PERMISSIVE_STRATEGIES: Readonly<PermissiveStrategies>;
63
+ /** Normalize a backend strategy bag to fully-specified booleans (backend-part combinable). */
64
+ export declare function resolvePermissiveStrategies(bag?: Partial<PermissiveStrategies>): PermissiveStrategies;
65
+ export declare const Config: z<PermGateConfig>;
66
+ export type ResolvedPermGateConfig = Required<Pick<PermGateConfig, 'caseInsensitivePaths' | 'grantTtlMs' | 'grantMaxUses' | 'permissive' | 'riskTimeoutMs' | 'riskLearning' | 'riskThreshold'>> & Pick<PermGateConfig, 'rulesFile' | 'dshHome' | 'defaultAction' | 'classifierEnabled' | 'classifierEndpoint' | 'classifierModel' | 'classifierApiKey' | 'learningFile' | 'eventsFile'> & {
67
+ readonly permissiveStrategies: PermissiveStrategies;
68
+ };
69
+ export declare function resolveConfig(config?: PermGateConfig): ResolvedPermGateConfig;
package/lib/config.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Plugin configuration for dsh-perm-gate, defined with Schemastery so the DSH
3
+ * loader validates and fills defaults before `apply`. Invalid values fail loud.
4
+ */
5
+ import z from '@deepseek-ai/schemastery';
6
+ export const DEFAULT_PERMISSIVE_STRATEGIES = {
7
+ trustAutoAllow: true,
8
+ alwaysConfirm: false,
9
+ llmAssist: false,
10
+ };
11
+ /** Normalize a backend strategy bag to fully-specified booleans (backend-part combinable). */
12
+ export function resolvePermissiveStrategies(bag = {}) {
13
+ return {
14
+ trustAutoAllow: bag.trustAutoAllow ?? DEFAULT_PERMISSIVE_STRATEGIES.trustAutoAllow,
15
+ alwaysConfirm: bag.alwaysConfirm ?? DEFAULT_PERMISSIVE_STRATEGIES.alwaysConfirm,
16
+ llmAssist: bag.llmAssist ?? DEFAULT_PERMISSIVE_STRATEGIES.llmAssist,
17
+ };
18
+ }
19
+ export const Config = z.object({
20
+ rulesFile: z.string(),
21
+ dshHome: z.string(),
22
+ defaultAction: z.union(['allow', 'ask', 'deny']).default('ask'),
23
+ caseInsensitivePaths: z.boolean().default(true),
24
+ classifierEnabled: z.boolean().default(false),
25
+ classifierEndpoint: z.string(),
26
+ classifierModel: z.string().default('deepseek-chat'),
27
+ classifierApiKey: z.string(),
28
+ riskTimeoutMs: z.number().min(1000).default(20_000),
29
+ riskLearning: z.boolean().default(false),
30
+ riskThreshold: z.number().min(1).max(10).default(3),
31
+ learningFile: z.string(),
32
+ eventsFile: z.string(),
33
+ grantTtlMs: z.number().min(1).default(5 * 60_000),
34
+ grantMaxUses: z.number().min(1).default(1),
35
+ permissive: z.boolean().default(false),
36
+ permissiveStrategies: z.object({
37
+ trustAutoAllow: z.boolean().default(true),
38
+ alwaysConfirm: z.boolean().default(false),
39
+ llmAssist: z.boolean().default(false),
40
+ }),
41
+ allowlist: z.array(z.string()),
42
+ });
43
+ export function resolveConfig(config = {}) {
44
+ const parsed = Config(config);
45
+ return {
46
+ rulesFile: parsed.rulesFile,
47
+ dshHome: parsed.dshHome,
48
+ defaultAction: parsed.defaultAction,
49
+ caseInsensitivePaths: parsed.caseInsensitivePaths ?? true,
50
+ classifierEnabled: parsed.classifierEnabled ?? false,
51
+ classifierEndpoint: parsed.classifierEndpoint,
52
+ classifierModel: parsed.classifierModel ?? 'deepseek-chat',
53
+ classifierApiKey: parsed.classifierApiKey,
54
+ riskTimeoutMs: parsed.riskTimeoutMs ?? 20_000,
55
+ riskLearning: parsed.riskLearning ?? false,
56
+ riskThreshold: parsed.riskThreshold ?? 3,
57
+ learningFile: parsed.learningFile,
58
+ eventsFile: parsed.eventsFile,
59
+ grantTtlMs: parsed.grantTtlMs ?? 5 * 60_000,
60
+ grantMaxUses: parsed.grantMaxUses ?? 1,
61
+ permissive: parsed.permissive ?? false,
62
+ permissiveStrategies: resolvePermissiveStrategies(parsed.permissiveStrategies),
63
+ };
64
+ }
@@ -0,0 +1,17 @@
1
+ import { type Decision, type ToolCallContext } from './evaluate.js';
2
+ export type EngineStage = 'hard-deny' | 'grant' | 'rule' | 'default' | 'ask' | 'classifier';
3
+ /**
4
+ * Deterministic P0 hard-deny reason, or undefined when the call may proceed to
5
+ * later stages.
6
+ */
7
+ export declare function hardDenyReason(ctx: ToolCallContext): string | undefined;
8
+ export interface GrantResolver {
9
+ (tool: string, args: Record<string, unknown>): 'allow' | 'no-match';
10
+ }
11
+ export interface FinalDecision extends Decision {
12
+ readonly stage: EngineStage;
13
+ }
14
+ /** Pure P0–P2 + P4 decision; P3 handled by the caller before falling back to ask. */
15
+ export declare function decide(ctx: ToolCallContext, ruleset: {
16
+ decide: (c: ToolCallContext) => Decision;
17
+ }, grants: GrantResolver): FinalDecision;
package/lib/engine.js ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The P0–P4 decision engine for dsh-perm-gate.
3
+ *
4
+ * P0 hard-deny deterministic, monotonic, never negotiated
5
+ * P1 session grant a precise one-shot/limited grant
6
+ * P2 static rules deny-first allow/ask chain
7
+ * P3 LLM classifier optional second-model review (off by default)
8
+ * P4 ask official approval seam
9
+ * Strictly fail-closed: P0 wins over every later stage.
10
+ */
11
+ import { isProtectedDestructiveTarget, isSensitivePath, isWithin } from './path.js';
12
+ import { isForceDeletion, isRecursiveDeletion, decomposeShellCommand } from './shell.js';
13
+ const DESTRUCTIVE_TOOL = /(?:^|[_-])(?:delete|remove|rm|destroy|erase|purge|wipe|unlink|rmdir|reset)(?:$|[_-]|recursive)/i;
14
+ const READ_TOOLS = new Set(['read', 'read_image', 'grep', 'glob', 'ls', 'lsp']);
15
+ function serialized(args) {
16
+ try {
17
+ return JSON.stringify(args);
18
+ }
19
+ catch {
20
+ return '';
21
+ }
22
+ }
23
+ function containsCredentialMaterial(args) {
24
+ return /(?:BEGIN (?:RSA |OPENSSH )?PRIVATE KEY|\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{8,}\b|Bearer\s+[A-Za-z0-9._~+\/-]{8,}|\.ssh[\\/](?:id_|config)|credentials\.yaml)/i
25
+ .test(serialized(args));
26
+ }
27
+ function pathArgument(args) {
28
+ for (const key of ['file_path', 'path', 'cwd', 'workdir']) {
29
+ const v = args[key];
30
+ if (typeof v === 'string')
31
+ return v;
32
+ }
33
+ return undefined;
34
+ }
35
+ /**
36
+ * Deterministic P0 hard-deny reason, or undefined when the call may proceed to
37
+ * later stages.
38
+ */
39
+ export function hardDenyReason(ctx) {
40
+ const args = ctx.args;
41
+ if (containsCredentialMaterial(args))
42
+ return 'call contains credential or private-key material';
43
+ const shell = /^(?:bash|pwsh|sh|cmd)$/.test(ctx.tool);
44
+ if (shell && typeof args.command === 'string') {
45
+ let commands = [];
46
+ try {
47
+ commands = decomposeShellCommand(args.command).commands;
48
+ }
49
+ catch {
50
+ return undefined; // undecidable syntax -> route to ask (P4), never allow
51
+ }
52
+ for (const cmd of commands) {
53
+ if (cmd.command === 'rm' && (isRecursiveDeletion(cmd) || isForceDeletion(cmd))) {
54
+ // Guarded further by path rules; root-level deletion denied below only
55
+ // when the explicit target is a protected root.
56
+ }
57
+ const redir = cmd.redirects.find((r) => isProtectedDestructiveTarget(r, ctx.dshHome));
58
+ if (redir !== undefined)
59
+ return `shell redirect targets protected path: ${redir}`;
60
+ }
61
+ }
62
+ const path = pathArgument(args);
63
+ if (path !== undefined && (DESTRUCTIVE_TOOL.test(ctx.tool) || ctx.tool === 'bash' || ctx.tool === 'pwsh' || ctx.tool === 'write' || ctx.tool === 'edit')) {
64
+ if (isProtectedDestructiveTarget(path, ctx.dshHome))
65
+ return `mutation targets protected path: ${path}`;
66
+ }
67
+ if (READ_TOOLS.has(ctx.tool) && path !== undefined) {
68
+ const normalized = path.startsWith('/') || /^[A-Za-z]:/.test(path) ? path : path;
69
+ if (!isWithin(ctx.cwd, normalized) && isSensitivePath(normalized)) {
70
+ return `read of sensitive path outside workspace: ${path}`;
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ /** Pure P0–P2 + P4 decision; P3 handled by the caller before falling back to ask. */
76
+ export function decide(ctx, ruleset, grants) {
77
+ const hard = hardDenyReason(ctx);
78
+ if (hard !== undefined)
79
+ return { action: 'deny', reason: `[hard-deny] ${hard}`, stage: 'hard-deny', ruleIndex: undefined };
80
+ if (grants(ctx.tool, ctx.args) === 'allow') {
81
+ return { action: 'allow', reason: 'covered by session grant', stage: 'grant', ruleIndex: undefined };
82
+ }
83
+ const rule = ruleset.decide(ctx);
84
+ return { ...rule, stage: rule.action === 'allow' || rule.action === 'deny' ? 'rule' : 'default' };
85
+ }
@@ -0,0 +1,20 @@
1
+ import { type CompiledRuleEntry, type CompiledRuleset, type RuleAction } from './rule.js';
2
+ import { type SimpleCommand } from './shell.js';
3
+ export interface ToolCallContext {
4
+ readonly tool: string;
5
+ readonly args: Record<string, unknown>;
6
+ readonly commandText?: string;
7
+ readonly cwd: string;
8
+ readonly home?: string;
9
+ readonly dshHome?: string;
10
+ readonly caseInsensitive?: boolean;
11
+ }
12
+ export interface Decision {
13
+ readonly action: RuleAction;
14
+ readonly reason: string;
15
+ readonly ruleIndex: number | undefined;
16
+ }
17
+ /** Whether one compiled rule matches the given call context. */
18
+ export declare function ruleMatches(rule: CompiledRuleEntry, ctx: ToolCallContext, commands: readonly SimpleCommand[], pathCands: readonly string[], urlCands: readonly string[]): boolean;
19
+ /** Compute the deterministic first-match decision (P2 chain). */
20
+ export declare function decideRules(ruleset: CompiledRuleset, ctx: ToolCallContext): Decision;