opencode-polkit 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -4,8 +4,14 @@ Redirect `sudo`/`doas` to `pkexec` in [OpenCode](https://opencode.ai).
4
4
  Triggers the system's native polkit authentication dialog (KDE, GNOME, etc.)
5
5
  instead of requiring a terminal for password input.
6
6
 
7
- When polkit is unavailable, privilege escalation commands are blocked with a
8
- clear error message.
7
+ Like CachyOS Hello, the plugin does not check for a polkit agent up front:
8
+ `sudo` is always redirected to `pkexec` at execution time, and polkitd routes
9
+ the request to whatever agent is currently registered. Without an agent,
10
+ `pkexec` fails fast with `Error creating textual authentication agent`
11
+ (no TTY); the plugin reports that as a clear denial. A hang (dialog never
12
+ appears) is bounded by the bash tool's own timeout (default 2 min,
13
+ configurable up to 10 min) and left untranslated — it cannot be told
14
+ apart from a long-running command.
9
15
 
10
16
  ## Install
11
17
 
@@ -21,17 +27,44 @@ Or add to `opencode.json` / `~/.config/opencode/opencode.jsonc`:
21
27
  }
22
28
  ```
23
29
 
30
+ For local development, point opencode at the project directory instead:
31
+
32
+ ```json
33
+ {
34
+ "plugin": ["/path/to/opencode-polkit"]
35
+ }
36
+ ```
37
+
24
38
  ## Behavior
25
39
 
26
- | Command | polkit available | polkit unavailable |
27
- |-----------------|--------------------------------|----------------------|
28
- | `sudo xxx` | redirects to `pkexec xxx` | blocked with error |
29
- | `doas xxx` | redirects to `pkexec xxx` | blocked with error |
30
- | `pkexec xxx` | passes through | blocked with error |
31
- | `sudoedit` | blocked | blocked |
32
- | `visudo` | blocked | blocked |
40
+ | Command | result |
41
+ |-----------------------|-------------------------------------------------------|
42
+ | `sudo xxx` | redirects to `pkexec xxx` |
43
+ | `doas xxx` | redirects to `pkexec xxx` |
44
+ | `cat x \| sudo tee y` | redirects to `cat x \| pkexec tee y` (mid-command) |
45
+ | `pkexec xxx` | passes through |
46
+ | `sudoedit` / `visudo` | blocked |
47
+
48
+ ### Minimal intervention
49
+
50
+ The command is rewritten **only** by replacing `sudo`/`doas` with
51
+ `pkexec` — its shape is otherwise untouched, so the agent always sees a
52
+ command that behaves like the one it wrote. Privilege keywords are found
53
+ by a lexical scanner (quote/escape/heredoc aware): `sudo` inside string
54
+ literals, comments or heredocs is ignored; `sudo` in any executable
55
+ position (leading, after `&&`/`||`/`|`, in `$(...)` or subshells) is
56
+ rewritten. `sudo a && sudo b` rewrites both.
57
+
58
+ ### Failures
33
59
 
34
- When a user denies the polkit authentication dialog, a clear error message is shown.
60
+ Unambiguous authentication failures are reported as clear errors and the
61
+ command is remembered so a retry is rejected without prompting again
62
+ (cleared when opencode restarts): `Not authorized`, `Error executing
63
+ command as another user`, `Error creating textual authentication agent`
64
+ (no polkit agent + no TTY). A hang (dialog never appears) is bounded by
65
+ the bash tool's own timeout (default 2 min, configurable up to 10 min)
66
+ and is left untranslated — it cannot be told apart from a long-running
67
+ command.
35
68
 
36
69
  ## i18n
37
70
 
package/dist/index.js CHANGED
@@ -1,77 +1,138 @@
1
- const MATCH = /(?<![-\/.\w])(sudo|pkexec|doas|sudoedit|visudo)(?![-\/.\w])/i;
2
- const POLKIT_AGENTS = [
3
- "polkit-kde-authentication-agent-1",
4
- "polkit-gnome-authentication-agent-1",
5
- "lxqt-policykit-agent",
6
- "mate-polkit",
7
- "xfce-polkit",
8
- "dde-polkit-agent",
9
- ];
1
+ /**
2
+ * Lexical scan for privilege-escalation keywords OUTSIDE quotes.
3
+ *
4
+ * Shell commands may contain `sudo` anywhere a command starts (pipes,
5
+ * `&&`/`||`, subshells, command substitution), and string literals /
6
+ * comments may contain the same words spuriously. A plain regex cannot
7
+ * tell those apart; this scanner tracks quote state (' " and backslash
8
+ * escapes) and only reports keywords in executable position.
9
+ *
10
+ * - `node -e "…sudo…"` → no hit (inside quotes)
11
+ * - `cat x | sudo tee y` → hit at index 8 (command boundary)
12
+ * - `cd /tmp && sudo ./x` → hit (after `&&`)
13
+ */
14
+ function scanPrivileged(command) {
15
+ const hits = [];
16
+ let inSingle = false;
17
+ let inDouble = false;
18
+ let escaped = false;
19
+ // Heredoc delimiters waiting to close (stack; multiple << are legal).
20
+ const heredocs = [];
21
+ for (let i = 0; i < command.length; i++) {
22
+ const ch = command[i];
23
+ // Inside a heredoc body: only a line consisting of the delimiter ends
24
+ // it. Content there is a string literal, never a command.
25
+ if (heredocs.length > 0) {
26
+ const lineEnd = command.indexOf("\n", i);
27
+ const line = lineEnd === -1 ? command.slice(i) : command.slice(i, lineEnd);
28
+ if (heredocs.some((d) => line === d)) {
29
+ heredocs.pop();
30
+ }
31
+ i = lineEnd === -1 ? command.length : lineEnd;
32
+ continue;
33
+ }
34
+ if (escaped) {
35
+ escaped = false;
36
+ continue;
37
+ }
38
+ if (ch === "\\" && !inSingle) {
39
+ escaped = true;
40
+ continue;
41
+ }
42
+ if (ch === "'" && !inDouble) {
43
+ inSingle = !inSingle;
44
+ continue;
45
+ }
46
+ if (ch === '"' && !inSingle) {
47
+ inDouble = !inDouble;
48
+ continue;
49
+ }
50
+ if (inSingle || inDouble)
51
+ continue;
52
+ // Heredoc start: `<<[-]DELIM` (delim may be quoted or unquoted).
53
+ if (ch === "<" && command[i + 1] === "<") {
54
+ let j = i + 2;
55
+ if (command[j] === "-")
56
+ j++;
57
+ while (j < command.length && /\s/.test(command[j]))
58
+ j++;
59
+ if (j < command.length && (command[j] === "'" || command[j] === '"')) {
60
+ const q = command[j];
61
+ const end = command.indexOf(q, j + 1);
62
+ if (end !== -1) {
63
+ heredocs.push(command.slice(j + 1, end));
64
+ i = end;
65
+ continue;
66
+ }
67
+ }
68
+ else {
69
+ const m = /^[^\s|;&<>()]+/.exec(command.slice(j));
70
+ if (m) {
71
+ heredocs.push(m[0]);
72
+ i = j + m[0].length - 1;
73
+ continue;
74
+ }
75
+ }
76
+ }
77
+ const prev = i > 0 ? command[i - 1] : "";
78
+ if (/[-\/.\w]/.test(prev))
79
+ continue;
80
+ for (const kw of ["sudoedit", "visudo", "sudo", "doas", "pkexec"]) {
81
+ if (command.startsWith(kw, i)) {
82
+ const next = command[i + kw.length] ?? "";
83
+ if (!/[-\/.\w]/.test(next)) {
84
+ hits.push({ index: i, kw });
85
+ i += kw.length - 1;
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return hits;
92
+ }
10
93
  const translations = {
11
94
  en: {
12
95
  blocked: "Privilege escalation command blocked by plugin",
13
- noPolkit: "sudo/doas blocked: no polkit agent detected, cannot escalate safely",
14
- noPolkitPkexec: "pkexec unavailable: no polkit agent detected",
15
96
  polkitDenied: "Polkit authentication denied by user",
16
97
  },
17
98
  zh: {
18
99
  blocked: "权限提升命令已被插件拦截",
19
- noPolkit: "sudo/doas 已被拦截:未检测到 polkit agent,无法安全提权",
20
- noPolkitPkexec: "pkexec 不可用:未检测到 polkit agent",
21
100
  polkitDenied: "Polkit 认证已被用户拒绝",
22
101
  },
23
102
  ja: {
24
103
  blocked: "特権昇格コマンドはプラグインによりブロックされました",
25
- noPolkit: "sudo/doas がブロックされました:polkit エージェントが見つからないため、安全に昇格できません",
26
- noPolkitPkexec: "pkexec は利用できません:polkit エージェントが見つかりません",
27
104
  polkitDenied: "Polkit 認証がユーザーによって拒否されました",
28
105
  },
29
106
  ko: {
30
107
  blocked: "권한 상승 명령이 플러그인에 의해 차단되었습니다",
31
- noPolkit: "sudo/doas 차단됨: polkit 에이전트가 감지되지 않아 안전하게 권한 상승할 수 없습니다",
32
- noPolkitPkexec: "pkexec 사용 불가: polkit 에이전트가 감지되지 않았습니다",
33
108
  polkitDenied: "Polkit 인증이 사용자에 의해 거부되었습니다",
34
109
  },
35
110
  de: {
36
111
  blocked: "Berechtigungseskalation vom Plugin blockiert",
37
- noPolkit: "sudo/doas blockiert: kein polkit-Agent erkannt, sichere Eskalation nicht möglich",
38
- noPolkitPkexec: "pkexec nicht verfügbar: kein polkit-Agent erkannt",
39
112
  polkitDenied: "Polkit-Authentifizierung vom Benutzer abgelehnt",
40
113
  },
41
114
  fr: {
42
115
  blocked: "Commande d'élévation de privilèges bloquée par le plugin",
43
- noPolkit: "sudo/doas bloqué : aucun agent polkit détecté, élévation impossible",
44
- noPolkitPkexec: "pkexec indisponible : aucun agent polkit détecté",
45
116
  polkitDenied: "Authentification polkit refusée par l'utilisateur",
46
117
  },
47
118
  es: {
48
119
  blocked: "Comando de elevación de privilegios bloqueado por el plugin",
49
- noPolkit: "sudo/doas bloqueado: no se detectó agente polkit, no se puede elevar de forma segura",
50
- noPolkitPkexec: "pkexec no disponible: no se detectó agente polkit",
51
120
  polkitDenied: "Autenticación polkit denegada por el usuario",
52
121
  },
53
122
  pt: {
54
123
  blocked: "Comando de elevação de privilégios bloqueado pelo plugin",
55
- noPolkit: "sudo/doas bloqueado: nenhum agente polkit detectado, não é possível elevar com segurança",
56
- noPolkitPkexec: "pkexec indisponível: nenhum agente polkit detectado",
57
124
  polkitDenied: "Autenticação polkit negada pelo usuário",
58
125
  },
59
126
  ru: {
60
127
  blocked: "Команда повышения привилегий заблокирована плагином",
61
- noPolkit: "sudo/doas заблокирован: не обнаружен polkit-агент, невозможно безопасно повысить привилегии",
62
- noPolkitPkexec: "pkexec недоступен: не обнаружен polkit-агент",
63
128
  polkitDenied: "Аутентификация polkit отклонена пользователем",
64
129
  },
65
130
  tr: {
66
131
  blocked: "Yetki yükseltme komutu eklenti tarafından engellendi",
67
- noPolkit: "sudo/doas engellendi: polkit aracısı algılanmadı, güvenli şekilde yetki yükseltilemez",
68
- noPolkitPkexec: "pkexec kullanılamıyor: polkit aracısı algılanmadı",
69
132
  polkitDenied: "Polkit kimlik doğrulaması kullanıcı tarafından reddedildi",
70
133
  },
71
134
  uk: {
72
135
  blocked: "Команду підвищення привілеїв заблоковано плагіном",
73
- noPolkit: "sudo/doas заблоковано: не виявлено агента polkit, неможливо безпечно підвищити привілеї",
74
- noPolkitPkexec: "pkexec недоступний: не виявлено агента polkit",
75
136
  polkitDenied: "Автентифікацію polkit відхилено користувачем",
76
137
  },
77
138
  };
@@ -85,29 +146,6 @@ function detectLocale() {
85
146
  return "en";
86
147
  }
87
148
  const MSG = translations[detectLocale()] ?? translations.en;
88
- function polkitAvailable() {
89
- try {
90
- if (process.env.DBUS_SESSION_BUS_ADDRESS) {
91
- const proc = Bun.spawnSync(["busctl", "--user", "list"], {
92
- stdio: ["ignore", "pipe", "pipe"],
93
- });
94
- if (proc.exitCode === 0 && /\bpolkit\b/i.test(new TextDecoder().decode(proc.stdout))) {
95
- return true;
96
- }
97
- }
98
- }
99
- catch { /* fall through */ }
100
- try {
101
- const proc = Bun.spawnSync(["pgrep", "-x", ...POLKIT_AGENTS], {
102
- stdio: ["ignore", "pipe", "pipe"],
103
- });
104
- return proc.exitCode === 0;
105
- }
106
- catch {
107
- return false;
108
- }
109
- }
110
- const hasPolkit = polkitAvailable();
111
149
  export const PolkitPlugin = async (_input) => {
112
150
  const deniedCommands = new Set();
113
151
  return {
@@ -115,37 +153,56 @@ export const PolkitPlugin = async (_input) => {
115
153
  if (hookInput.tool !== "bash")
116
154
  return;
117
155
  const command = hookOutput?.args?.command ?? "";
118
- if (!MATCH.test(command))
156
+ const hits = scanPrivileged(command);
157
+ if (hits.length === 0)
119
158
  return;
120
159
  if (deniedCommands.has(command)) {
121
160
  throw new Error(MSG.polkitDenied);
122
161
  }
123
- if (/^pkexec(?![-\/.\w])/.test(command.trim())) {
124
- if (!hasPolkit)
125
- throw new Error(MSG.noPolkitPkexec);
126
- return;
127
- }
128
- if (/(?<![-\/.\w])(sudoedit|visudo)(?![-\/.\w])/.test(command)) {
162
+ if (hits.some((h) => h.kw === "sudoedit" || h.kw === "visudo")) {
129
163
  throw new Error(MSG.blocked);
130
164
  }
131
- if (!hasPolkit) {
132
- throw new Error(MSG.noPolkit);
165
+ // Already pkexec (leading or mid-command): pass through untouched.
166
+ if (hits.every((h) => h.kw === "pkexec"))
167
+ return;
168
+ // Rewrite every in-command sudo/doas to pkexec (back to front so
169
+ // indices stay valid): `cat x | sudo tee y` -> `cat x | pkexec tee y`.
170
+ // No other rewriting: the command keeps its exact shape so the agent
171
+ // sees only the minimal change (sudo -> pkexec).
172
+ let rewritten = command;
173
+ for (const h of [...hits].reverse()) {
174
+ if (h.kw === "sudo" || h.kw === "doas") {
175
+ rewritten =
176
+ rewritten.slice(0, h.index) + "pkexec" + rewritten.slice(h.index + h.kw.length);
177
+ }
133
178
  }
134
- hookOutput.args.command = command.replace(/(?<![-\/.\w])(sudo|doas)(?![-\/.\w])/, "pkexec");
179
+ hookOutput.args.command = rewritten;
135
180
  },
136
181
  "tool.execute.after": async (hookInput, hookOutput) => {
137
182
  if (hookInput.tool !== "bash")
138
183
  return;
139
184
  const command = hookInput.args?.command ?? "";
140
- if (!MATCH.test(command))
185
+ const hits = scanPrivileged(command);
186
+ if (hits.length === 0)
141
187
  return;
142
- const originalCommand = command.replace(/(?<![-\/.\w])pkexec(?![-\/.\w])/, "sudo");
188
+ // Reconstruct the user's original command for the deny list: map
189
+ // pkexec back to sudo.
190
+ let original = command;
191
+ for (const h of [...scanPrivileged(original)].reverse()) {
192
+ if (h.kw === "pkexec") {
193
+ original = original.slice(0, h.index) + "sudo" + original.slice(h.index + 6);
194
+ }
195
+ }
196
+ // Only unambiguous authentication failures are reported and deny
197
+ // listed. A hang (dialog never appears) is bounded by the bash tool's
198
+ // own timeout (default 2 min) and is NOT translated here: it cannot
199
+ // be told apart from a long-running command.
143
200
  if (/\bNot authorized\b/.test(hookOutput.output) ||
144
- /\bError executing command as another user\b/.test(hookOutput.output)) {
145
- deniedCommands.add(originalCommand);
201
+ /\bError executing command as another user\b/.test(hookOutput.output) ||
202
+ /\bError creating textual authentication agent\b/.test(hookOutput.output)) {
203
+ deniedCommands.add(original);
146
204
  throw new Error(MSG.polkitDenied);
147
205
  }
148
206
  },
149
207
  };
150
208
  };
151
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-polkit",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Redirect sudo/doas to pkexec in OpenCode — uses system polkit agent for native auth dialogs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,8 +17,8 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "scripts": {
20
- "build": "tsc",
21
- "prepublishOnly": "npm run build"
20
+ "build": "deno task build",
21
+ "check": "deno task check"
22
22
  },
23
23
  "engines": {
24
24
  "opencode": ">=1.0.0"
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { Plugin } from "@opencode-ai/plugin";
2
- export declare const PolkitPlugin: Plugin;
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AA2H9D,eAAO,MAAM,YAAY,EAAE,MAgD1B,CAAA"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}