opencode-polkit 0.1.4 → 0.1.5
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 +12 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +103 -6
- package/dist/index.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ For local development, point opencode at the project directory instead:
|
|
|
41
41
|
|-----------------------|-------------------------------------------------------|
|
|
42
42
|
| `sudo xxx` | redirects to `pkexec xxx` |
|
|
43
43
|
| `doas xxx` | redirects to `pkexec xxx` |
|
|
44
|
+
| `sudo -n xxx` | blocked: pkexec has no short options (unclear failure)|
|
|
44
45
|
| `cat x \| sudo tee y` | redirects to `cat x \| pkexec tee y` (mid-command) |
|
|
45
46
|
| `pkexec xxx` | passes through |
|
|
46
47
|
| `sudoedit` / `visudo` | blocked |
|
|
@@ -52,8 +53,10 @@ The command is rewritten **only** by replacing `sudo`/`doas` with
|
|
|
52
53
|
command that behaves like the one it wrote. Privilege keywords are found
|
|
53
54
|
by a lexical scanner (quote/escape/heredoc aware): `sudo` inside string
|
|
54
55
|
literals, comments or heredocs is ignored; `sudo` in any executable
|
|
55
|
-
position (leading, after `&&`/`||`/`|`, in `$(...)` or subshells
|
|
56
|
-
|
|
56
|
+
position (leading, after `&&`/`||`/`|`, in `$(...)` or subshells, after
|
|
57
|
+
an env assignment like `FOO=1 sudo x`) is rewritten, while the same word
|
|
58
|
+
in argument position (`--name sudo bash`) is left alone. `sudo a && sudo b`
|
|
59
|
+
rewrites both.
|
|
57
60
|
|
|
58
61
|
### Failures
|
|
59
62
|
|
|
@@ -66,6 +69,13 @@ the bash tool's own timeout (default 2 min, configurable up to 10 min)
|
|
|
66
69
|
and is left untranslated — it cannot be told apart from a long-running
|
|
67
70
|
command.
|
|
68
71
|
|
|
72
|
+
Options pkexec does not accept (all short options, plus any long option
|
|
73
|
+
outside `--user`, `--keep-cwd`, `--disable-internal-agent`, `--help`,
|
|
74
|
+
`--version`) are rejected before execution with a clear message, since
|
|
75
|
+
the rewrite would otherwise surface pkexec's own confusing
|
|
76
|
+
`Cannot run program -n` error. `sudo --` and options after the program
|
|
77
|
+
name are left alone.
|
|
78
|
+
|
|
69
79
|
## i18n
|
|
70
80
|
|
|
71
81
|
Messages adapt to `$LC_MESSAGES` / `$LANG`. Currently supports:
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
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
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** Long options pkexec accepts (all of them; pkexec has no short options). */
|
|
2
|
+
const PKEXEC_OPTIONS = new Set([
|
|
3
|
+
"--version",
|
|
4
|
+
"--help",
|
|
5
|
+
"--disable-internal-agent",
|
|
6
|
+
"--keep-cwd",
|
|
7
|
+
"--user",
|
|
8
|
+
]);
|
|
1
9
|
/**
|
|
2
10
|
* Lexical scan for privilege-escalation keywords OUTSIDE quotes.
|
|
3
11
|
*
|
|
@@ -10,23 +18,31 @@
|
|
|
10
18
|
* - `node -e "…sudo…"` → no hit (inside quotes)
|
|
11
19
|
* - `cat x | sudo tee y` → hit at index 8 (command boundary)
|
|
12
20
|
* - `cd /tmp && sudo ./x` → hit (after `&&`)
|
|
21
|
+
* - `--name sudo bash` → no hit (argument position)
|
|
22
|
+
* - `echo x; # sudo y` → no hit (comment)
|
|
13
23
|
*/
|
|
14
24
|
function scanPrivileged(command) {
|
|
15
25
|
const hits = [];
|
|
16
26
|
let inSingle = false;
|
|
17
27
|
let inDouble = false;
|
|
18
28
|
let escaped = false;
|
|
29
|
+
// True right after a command boundary (`;`, `&&`, `||`, `|`, `(`, start).
|
|
30
|
+
let atCommandStart = true;
|
|
19
31
|
// Heredoc delimiters waiting to close (stack; multiple << are legal).
|
|
20
32
|
const heredocs = [];
|
|
21
33
|
for (let i = 0; i < command.length; i++) {
|
|
22
34
|
const ch = command[i];
|
|
23
35
|
// Inside a heredoc body: only a line consisting of the delimiter ends
|
|
24
|
-
// it. Content there is a string literal, never a command.
|
|
36
|
+
// it. Content there is a string literal, never a command. Pop the
|
|
37
|
+
// matching delimiter (nested heredocs close in any order).
|
|
25
38
|
if (heredocs.length > 0) {
|
|
26
39
|
const lineEnd = command.indexOf("\n", i);
|
|
27
40
|
const line = lineEnd === -1 ? command.slice(i) : command.slice(i, lineEnd);
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
const idx = heredocs.lastIndexOf(line);
|
|
42
|
+
if (idx !== -1) {
|
|
43
|
+
heredocs.splice(idx);
|
|
44
|
+
// The line after the delimiter starts a new command position.
|
|
45
|
+
atCommandStart = true;
|
|
30
46
|
}
|
|
31
47
|
i = lineEnd === -1 ? command.length : lineEnd;
|
|
32
48
|
continue;
|
|
@@ -43,7 +59,7 @@ function scanPrivileged(command) {
|
|
|
43
59
|
inSingle = !inSingle;
|
|
44
60
|
continue;
|
|
45
61
|
}
|
|
46
|
-
if (ch === '"' && !
|
|
62
|
+
if (ch === '"' && !inDouble) {
|
|
47
63
|
inDouble = !inDouble;
|
|
48
64
|
continue;
|
|
49
65
|
}
|
|
@@ -74,8 +90,37 @@ function scanPrivileged(command) {
|
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
|
-
|
|
78
|
-
if (
|
|
93
|
+
// Command boundaries open a new executable position.
|
|
94
|
+
if (ch === ";" || ch === "|" || ch === "&" || ch === "(" || ch === "\n") {
|
|
95
|
+
atCommandStart = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// `#` at a word start begins a comment that runs to end of line.
|
|
99
|
+
if (ch === "#") {
|
|
100
|
+
const prev = command[i - 1] ?? "";
|
|
101
|
+
if (i === 0 || /\s/.test(prev) || /[;|&(]/.test(prev)) {
|
|
102
|
+
const nl = command.indexOf("\n", i);
|
|
103
|
+
i = nl === -1 ? command.length : nl;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (/\s/.test(ch))
|
|
108
|
+
continue;
|
|
109
|
+
// An env assignment (`FOO=1 sudo x`) still starts a command; any other
|
|
110
|
+
// word in argument position (`--name sudo`) does not.
|
|
111
|
+
let isCommandPos = atCommandStart;
|
|
112
|
+
if (!isCommandPos) {
|
|
113
|
+
let j = i - 1;
|
|
114
|
+
while (j >= 0 && /\s/.test(command[j]))
|
|
115
|
+
j--;
|
|
116
|
+
while (j >= 0 && !/[\s;|&()]/.test(command[j]))
|
|
117
|
+
j--;
|
|
118
|
+
const token = command.slice(j + 1, i);
|
|
119
|
+
if (token.includes("="))
|
|
120
|
+
isCommandPos = true;
|
|
121
|
+
}
|
|
122
|
+
atCommandStart = false;
|
|
123
|
+
if (!isCommandPos)
|
|
79
124
|
continue;
|
|
80
125
|
for (const kw of ["sudoedit", "visudo", "sudo", "doas", "pkexec"]) {
|
|
81
126
|
if (command.startsWith(kw, i)) {
|
|
@@ -90,50 +135,93 @@ function scanPrivileged(command) {
|
|
|
90
135
|
}
|
|
91
136
|
return hits;
|
|
92
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* First option token after a privilege keyword that pkexec does not
|
|
140
|
+
* accept, or null when the command's options are pkexec-compatible.
|
|
141
|
+
* Scanning stops at the program name (`--` also terminates options, as
|
|
142
|
+
* in sudo); tokens after it belong to the program, not to pkexec.
|
|
143
|
+
*
|
|
144
|
+
* - `sudo -n true` → `-n` (pkexec has no short options)
|
|
145
|
+
* - `sudo --non-interactive x` → `--non-interactive`
|
|
146
|
+
* - `sudo --user root x` → null (compatible)
|
|
147
|
+
* - `sudo -- x -n` → null (`--` ends sudo options)
|
|
148
|
+
*/
|
|
149
|
+
function findBadOption(command, hit) {
|
|
150
|
+
let i = hit.index + hit.kw.length;
|
|
151
|
+
while (i < command.length && /\s/.test(command[i]))
|
|
152
|
+
i++;
|
|
153
|
+
while (i < command.length && command[i] === "-") {
|
|
154
|
+
let end = i + 1;
|
|
155
|
+
while (end < command.length && !/\s/.test(command[end]) && !/[;|&()]/.test(command[end]))
|
|
156
|
+
end++;
|
|
157
|
+
const token = command.slice(i, end);
|
|
158
|
+
if (token === "--")
|
|
159
|
+
break;
|
|
160
|
+
if (!token.startsWith("--"))
|
|
161
|
+
return token;
|
|
162
|
+
if (!PKEXEC_OPTIONS.has(token.split("=")[0]))
|
|
163
|
+
return token;
|
|
164
|
+
i = end;
|
|
165
|
+
while (i < command.length && /\s/.test(command[i]))
|
|
166
|
+
i++;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
93
170
|
const translations = {
|
|
94
171
|
en: {
|
|
95
172
|
blocked: "Privilege escalation command blocked by plugin",
|
|
96
173
|
polkitDenied: "Polkit authentication denied by user",
|
|
174
|
+
badOption: 'Option "{opt}" is not supported by pkexec. Remove it or use a pkexec-compatible one (--user, --keep-cwd, --disable-internal-agent).',
|
|
97
175
|
},
|
|
98
176
|
zh: {
|
|
99
177
|
blocked: "权限提升命令已被插件拦截",
|
|
100
178
|
polkitDenied: "Polkit 认证已被用户拒绝",
|
|
179
|
+
badOption: '选项 "{opt}" 不被 pkexec 支持,请移除或改用 pkexec 兼容选项(--user、--keep-cwd、--disable-internal-agent)。',
|
|
101
180
|
},
|
|
102
181
|
ja: {
|
|
103
182
|
blocked: "特権昇格コマンドはプラグインによりブロックされました",
|
|
104
183
|
polkitDenied: "Polkit 認証がユーザーによって拒否されました",
|
|
184
|
+
badOption: 'オプション "{opt}" は pkexec ではサポートされていません。削除するか、pkexec 互換オプション(--user、--keep-cwd、--disable-internal-agent)を使用してください。',
|
|
105
185
|
},
|
|
106
186
|
ko: {
|
|
107
187
|
blocked: "권한 상승 명령이 플러그인에 의해 차단되었습니다",
|
|
108
188
|
polkitDenied: "Polkit 인증이 사용자에 의해 거부되었습니다",
|
|
189
|
+
badOption: '옵션 "{opt}"은(는) pkexec에서 지원되지 않습니다. 제거하거나 pkexec 호환 옵션(--user, --keep-cwd, --disable-internal-agent)을 사용하세요.',
|
|
109
190
|
},
|
|
110
191
|
de: {
|
|
111
192
|
blocked: "Berechtigungseskalation vom Plugin blockiert",
|
|
112
193
|
polkitDenied: "Polkit-Authentifizierung vom Benutzer abgelehnt",
|
|
194
|
+
badOption: 'Option "{opt}" wird von pkexec nicht unterstützt. Entfernen Sie sie oder verwenden Sie eine pkexec-kompatible Option (--user, --keep-cwd, --disable-internal-agent).',
|
|
113
195
|
},
|
|
114
196
|
fr: {
|
|
115
197
|
blocked: "Commande d'élévation de privilèges bloquée par le plugin",
|
|
116
198
|
polkitDenied: "Authentification polkit refusée par l'utilisateur",
|
|
199
|
+
badOption: 'Option "{opt}" non prise en charge par pkexec. Supprimez-la ou utilisez une option compatible pkexec (--user, --keep-cwd, --disable-internal-agent).',
|
|
117
200
|
},
|
|
118
201
|
es: {
|
|
119
202
|
blocked: "Comando de elevación de privilegios bloqueado por el plugin",
|
|
120
203
|
polkitDenied: "Autenticación polkit denegada por el usuario",
|
|
204
|
+
badOption: 'Opción "{opt}" no admitida por pkexec. Elimínela o use una opción compatible con pkexec (--user, --keep-cwd, --disable-internal-agent).',
|
|
121
205
|
},
|
|
122
206
|
pt: {
|
|
123
207
|
blocked: "Comando de elevação de privilégios bloqueado pelo plugin",
|
|
124
208
|
polkitDenied: "Autenticação polkit negada pelo usuário",
|
|
209
|
+
badOption: 'Opção "{opt}" não suportada pelo pkexec. Remova-a ou use uma opção compatível com pkexec (--user, --keep-cwd, --disable-internal-agent).',
|
|
125
210
|
},
|
|
126
211
|
ru: {
|
|
127
212
|
blocked: "Команда повышения привилегий заблокирована плагином",
|
|
128
213
|
polkitDenied: "Аутентификация polkit отклонена пользователем",
|
|
214
|
+
badOption: 'Параметр "{opt}" не поддерживается pkexec. Удалите его или используйте совместимый параметр (--user, --keep-cwd, --disable-internal-agent).',
|
|
129
215
|
},
|
|
130
216
|
tr: {
|
|
131
217
|
blocked: "Yetki yükseltme komutu eklenti tarafından engellendi",
|
|
132
218
|
polkitDenied: "Polkit kimlik doğrulaması kullanıcı tarafından reddedildi",
|
|
219
|
+
badOption: '"{opt}" seçeneği pkexec tarafından desteklenmiyor. Kaldırın veya pkexec uyumlu bir seçenek kullanın (--user, --keep-cwd, --disable-internal-agent).',
|
|
133
220
|
},
|
|
134
221
|
uk: {
|
|
135
222
|
blocked: "Команду підвищення привілеїв заблоковано плагіном",
|
|
136
223
|
polkitDenied: "Автентифікацію polkit відхилено користувачем",
|
|
224
|
+
badOption: 'Параметр "{opt}" не підтримується pkexec. Видаліть його або використайте сумісний параметр (--user, --keep-cwd, --disable-internal-agent).',
|
|
137
225
|
},
|
|
138
226
|
};
|
|
139
227
|
function detectLocale() {
|
|
@@ -162,6 +250,15 @@ export const PolkitPlugin = async (_input) => {
|
|
|
162
250
|
if (hits.some((h) => h.kw === "sudoedit" || h.kw === "visudo")) {
|
|
163
251
|
throw new Error(MSG.blocked);
|
|
164
252
|
}
|
|
253
|
+
// Rewrite would produce `pkexec <bad option>`: fail with a clear
|
|
254
|
+
// message instead of a confusing `Cannot run program` error.
|
|
255
|
+
for (const h of hits) {
|
|
256
|
+
if (h.kw === "sudo" || h.kw === "doas" || h.kw === "pkexec") {
|
|
257
|
+
const bad = findBadOption(command, h);
|
|
258
|
+
if (bad)
|
|
259
|
+
throw new Error(MSG.badOption.replace("{opt}", bad));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
165
262
|
// Already pkexec (leading or mid-command): pass through untouched.
|
|
166
263
|
if (hits.every((h) => h.kw === "pkexec"))
|
|
167
264
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|