pi-polkit 0.1.0 → 0.1.1
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/CHANGELOG.md +24 -0
- package/README.md +31 -3
- package/package.json +3 -3
- package/polkit-redirect.ts +3 -3
- package/polkit-shims.ts +111 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.1] - 2026-11-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `polkit-shims.ts`: `su` stand-in. `su -c COMMAND [USER]` (also `-cCOMMAND`,
|
|
13
|
+
`--command COMMAND`, `--command=COMMAND`) becomes
|
|
14
|
+
`pkexec --keep-cwd --user USER sh -c COMMAND`, because a non-login `su`
|
|
15
|
+
keeps the working directory and runs its command through a shell. Every other
|
|
16
|
+
form is refused with status `64` and one line naming the alternative: `su`,
|
|
17
|
+
`su -`, `su -l` (login shell → `pkexec bash`), `-m`/`-p`/`-w` (pkexec
|
|
18
|
+
sanitizes the environment), `-s` (name the shell in the command), `-g`/`-G`
|
|
19
|
+
(pkexec cannot switch groups), `--pty` (no terminal), `-f` and
|
|
20
|
+
`--session-command`.
|
|
21
|
+
- README scope statement: the extension covers elevation → polkit only;
|
|
22
|
+
ssh, gpg/pinentry, keyrings, database clients, `kinit` and disk encryption
|
|
23
|
+
are explicitly out of scope and need an askpass helper or a PTY.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- README authorisation note: a desktop agent may reuse the password it already
|
|
28
|
+
holds, so a second command can run without a dialog; that cache belongs to
|
|
29
|
+
the agent and must not be relied on. `su -c … USER` authenticates an
|
|
30
|
+
administrator rather than `USER`, and runs the command with `sh -c`.
|
|
31
|
+
|
|
8
32
|
## [0.1.0] - 2026-11-01
|
|
9
33
|
|
|
10
34
|
### Added
|
package/README.md
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
# pi-polkit
|
|
2
2
|
|
|
3
|
-
Redirects `sudo` and `
|
|
3
|
+
Redirects `sudo`, `doas` and `su -c` to `pkexec` inside Pi, so authorisation
|
|
4
4
|
goes through the desktop's polkit agent (KDE, GNOME, …) instead of a terminal
|
|
5
5
|
the shell tools do not have. Applies to the `bash` tool and to `!` user
|
|
6
6
|
commands; `sudo` in other terminals is unchanged.
|
|
7
7
|
|
|
8
|
+
## Scope
|
|
9
|
+
|
|
10
|
+
This extension covers one category: **elevation → polkit**. It exists because
|
|
11
|
+
the `bash` tool has no terminal, so any program that reads a password from
|
|
12
|
+
`/dev/tty` fails there. `sudo`, `doas` and `su -c` are that category, and they
|
|
13
|
+
are routed to `pkexec`, which asks the desktop's polkit agent instead.
|
|
14
|
+
|
|
15
|
+
Password prompts that are not polkit's business are out of scope: `ssh`,
|
|
16
|
+
`ssh-add`, `gpg`/pinentry, keyrings (`secret-tool`, KWallet), database clients
|
|
17
|
+
(`psql`, `mysql -p`, `redis-cli`), `kinit`, and disk encryption (`cryptsetup`,
|
|
18
|
+
`fscrypt`, `gocryptfs`). They read the process's own terminal, so they need an
|
|
19
|
+
askpass helper or a pseudo-terminal, not polkit; mixing them in would make this
|
|
20
|
+
extension's boundary meaningless.
|
|
21
|
+
|
|
8
22
|
## Installation
|
|
9
23
|
|
|
10
24
|
Install as a Pi package (published on npm):
|
|
@@ -35,14 +49,18 @@ pi -e ./pi-polkit
|
|
|
35
49
|
| `sudo -n …` | refused: pkexec has no non-interactive mode |
|
|
36
50
|
| `sudo -i`, `-s`, `-b` | refused, with the equivalent command |
|
|
37
51
|
| `sudoedit`, `visudo` | refused, with the alternative |
|
|
52
|
+
| `su -c 'id'` | dialog, then runs as root |
|
|
53
|
+
| `su -c 'id' bob` | as above, as `bob` (administrator password) |
|
|
54
|
+
| `su`, `su -`, `su -l` | refused: no interactive shell, no terminal |
|
|
55
|
+
| `su -m`, `-s`, `-g`, `-w`, `--pty` | refused, with the alternative |
|
|
38
56
|
| `pkexec …` | passed through |
|
|
39
57
|
|
|
40
58
|
Refused invocations exit with status 64 and one line naming the alternative.
|
|
41
59
|
|
|
42
60
|
## How it works
|
|
43
61
|
|
|
44
|
-
`sudo` and `
|
|
45
|
-
those instead of the system binaries, and they forward the command to
|
|
62
|
+
`sudo`, `doas` and `su` stand-ins are placed first on `PATH`, so the shell
|
|
63
|
+
resolves those instead of the system binaries, and they forward the command to
|
|
46
64
|
`pkexec`. Command text is never rewritten, so quoting, pipes and subshells need
|
|
47
65
|
no special handling.
|
|
48
66
|
|
|
@@ -63,11 +81,21 @@ The shim directory is created lazily in `$XDG_RUNTIME_DIR` (falling back to
|
|
|
63
81
|
one; other actions may not, according to policy. The extension takes no part in
|
|
64
82
|
that decision.
|
|
65
83
|
|
|
84
|
+
`org.freedesktop.policykit.exec` asks for administrator authentication on every
|
|
85
|
+
call, but the desktop agent may keep the password it just received and answer
|
|
86
|
+
the next challenge itself (KDE's agent does). A second command in the same
|
|
87
|
+
session then runs without a dialog. That cache belongs to the agent: another
|
|
88
|
+
agent, a restarted agent or a dismissed dialog means the next command prompts
|
|
89
|
+
again, so do not rely on it.
|
|
90
|
+
|
|
66
91
|
## Limitations
|
|
67
92
|
|
|
68
93
|
- Absolute paths (`/usr/bin/sudo`) and relative ones (`./sudo`) bypass `PATH`.
|
|
69
94
|
- Commands that reset `PATH` (`env -i`, `PATH=/usr/bin …`) bypass it as well.
|
|
70
95
|
- Programs that invoke `sudo` with a scrubbed environment bypass it.
|
|
96
|
+
- `su -c … USER` authenticates an administrator (polkit), not `USER`, so an
|
|
97
|
+
account that real `su` would allow can be refused here.
|
|
98
|
+
- `su -c` runs the command with `sh -c`, not with the target user's login shell.
|
|
71
99
|
- PowerShell is not handled; the extension targets Linux only.
|
|
72
100
|
|
|
73
101
|
These bypass the redirect, not polkit, which still decides whether the command
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-polkit",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Redirect sudo
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Redirect sudo, doas and su to pkexec in Pi via PATH shims, so the desktop polkit agent handles authentication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"pi": {
|
|
7
7
|
"extensions": ["./index.ts"]
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=22.19.0"
|
|
29
29
|
},
|
|
30
|
-
"keywords": ["pi-package", "pi-extension", "sudo", "doas", "pkexec", "polkit", "security"],
|
|
30
|
+
"keywords": ["pi-package", "pi-extension", "sudo", "doas", "su", "pkexec", "polkit", "security"],
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
package/polkit-redirect.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { createBashToolDefinition, createLocalBashOperations } from "@earendil-w
|
|
|
3
3
|
import { polkitShimsPathExportLine, removePolkitShims, withPolkitShimsOnPath } from "./polkit-shims"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Redirects sudo and
|
|
6
|
+
* Redirects sudo, doas and su -c to pkexec inside Pi, so authorisation goes
|
|
7
7
|
* through the desktop's polkit agent (KDE, GNOME, …) instead of a terminal the
|
|
8
8
|
* shell tools do not have. Applies to the `bash` tool and to `!` user
|
|
9
9
|
* commands; `sudo` in other terminals is unchanged.
|
|
10
10
|
*
|
|
11
11
|
* The redirect is a PATH prepend, not a command rewrite: the shims in
|
|
12
|
-
* polkit-shims.ts stand in for the sudo/doas binaries, and the shell's own
|
|
12
|
+
* polkit-shims.ts stand in for the sudo/doas/su binaries, and the shell's own
|
|
13
13
|
* name resolution picks them up for `sudo`, `$(sudo …)`, `xargs sudo`,
|
|
14
14
|
* `FOO=1 sudo` and nested scripts alike. Nothing parses the command string,
|
|
15
15
|
* and nothing parses pkexec's output: the shims hand pkexec's exit status
|
|
@@ -32,7 +32,7 @@ export default function polkitRedirect(pi: ExtensionAPI): void {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Local shell operations whose commands resolve sudo through the shims. The
|
|
35
|
+
* Local shell operations whose commands resolve sudo and su through the shims. The
|
|
36
36
|
* `!` user commands run through pi's own bash backend with no prepared
|
|
37
37
|
* environment, so the PATH prepend happens as the first command line; the
|
|
38
38
|
* exit status and output of the command itself are unchanged.
|
package/polkit-shims.ts
CHANGED
|
@@ -174,6 +174,113 @@ fi
|
|
|
174
174
|
run_pkexec "$@"
|
|
175
175
|
`
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* The su shim. `su -c` is the only form pkexec can express exactly: a command
|
|
179
|
+
* run as the target user through a shell, in the current directory. Every
|
|
180
|
+
* other form asks for a login shell, an environment, a shell binary, a group
|
|
181
|
+
* or a terminal, none of which pkexec can honour, so it is refused instead.
|
|
182
|
+
*/
|
|
183
|
+
const SU_SHIM = `#!/bin/sh
|
|
184
|
+
# pi-polkit: su shim. Routes su -c to pkexec so polkit can authorize it.
|
|
185
|
+
# Generated at extension startup; do not edit.
|
|
186
|
+
|
|
187
|
+
fail() {
|
|
188
|
+
printf 'pi-polkit: %s\\n' "$1" >&2
|
|
189
|
+
exit ${SHIM_USAGE_EXIT}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
193
|
+
|
|
194
|
+
user=
|
|
195
|
+
cmd=
|
|
196
|
+
has_cmd=
|
|
197
|
+
while [ $# -gt 0 ]; do
|
|
198
|
+
case $1 in
|
|
199
|
+
--)
|
|
200
|
+
shift
|
|
201
|
+
break
|
|
202
|
+
;;
|
|
203
|
+
-c)
|
|
204
|
+
[ $# -ge 2 ] || fail 'su -c needs a command: su -c COMMAND [USER]'
|
|
205
|
+
cmd=$2
|
|
206
|
+
has_cmd=1
|
|
207
|
+
shift 2
|
|
208
|
+
;;
|
|
209
|
+
-c?*)
|
|
210
|
+
cmd=\${1#-c}
|
|
211
|
+
has_cmd=1
|
|
212
|
+
shift
|
|
213
|
+
;;
|
|
214
|
+
--command)
|
|
215
|
+
[ $# -ge 2 ] || fail 'su --command needs a command: su --command COMMAND [USER]'
|
|
216
|
+
cmd=$2
|
|
217
|
+
has_cmd=1
|
|
218
|
+
shift 2
|
|
219
|
+
;;
|
|
220
|
+
--command=*)
|
|
221
|
+
cmd=\${1#--command=}
|
|
222
|
+
has_cmd=1
|
|
223
|
+
shift
|
|
224
|
+
;;
|
|
225
|
+
- | -l | --login)
|
|
226
|
+
fail 'su starts a login shell; run pkexec bash instead.'
|
|
227
|
+
;;
|
|
228
|
+
-s | --shell | -s?* | --shell=*)
|
|
229
|
+
fail 'su -s picks the shell; name the shell in the command: pkexec sh -c ...'
|
|
230
|
+
;;
|
|
231
|
+
-m | -p | --preserve-environment)
|
|
232
|
+
fail 'pkexec deliberately sanitizes the environment; drop -m/-p.'
|
|
233
|
+
;;
|
|
234
|
+
-g | -G | --group | --supp-group | -g?* | -G?* | --group=* | --supp-group=*)
|
|
235
|
+
fail 'su -g/-G switches groups; pkexec can only switch users.'
|
|
236
|
+
;;
|
|
237
|
+
-w | --whitelist-environment | -w?* | --whitelist-environment=*)
|
|
238
|
+
fail 'su -w whitelists environment variables; pkexec sanitizes the environment.'
|
|
239
|
+
;;
|
|
240
|
+
-P | --pty)
|
|
241
|
+
fail 'su --pty asks for a terminal; the bash tool has none.'
|
|
242
|
+
;;
|
|
243
|
+
-f | --fast)
|
|
244
|
+
fail 'su -f is a shell flag; pass it to the shell in the command instead.'
|
|
245
|
+
;;
|
|
246
|
+
--session-command | --session-command=*)
|
|
247
|
+
fail 'su --session-command keeps the session; pkexec always starts a new process.'
|
|
248
|
+
;;
|
|
249
|
+
-h | --help | -V | --version)
|
|
250
|
+
fail "option has no pkexec equivalent: $1"
|
|
251
|
+
;;
|
|
252
|
+
-?*)
|
|
253
|
+
fail "unknown su option: $1"
|
|
254
|
+
;;
|
|
255
|
+
*)
|
|
256
|
+
[ -z "$user" ] || fail 'su passes arguments after the user name to the shell; use su -c COMMAND USER.'
|
|
257
|
+
user=$1
|
|
258
|
+
shift
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
done
|
|
262
|
+
|
|
263
|
+
# Operands after -- are the same user-then-nothing sequence.
|
|
264
|
+
while [ $# -gt 0 ]; do
|
|
265
|
+
[ -z "$user" ] || fail 'su passes arguments after the user name to the shell; use su -c COMMAND USER.'
|
|
266
|
+
user=$1
|
|
267
|
+
shift
|
|
268
|
+
done
|
|
269
|
+
|
|
270
|
+
[ -n "$user" ] || user=root
|
|
271
|
+
[ -n "$has_cmd" ] || fail 'su without -c opens an interactive shell; run pkexec bash instead.'
|
|
272
|
+
|
|
273
|
+
# su keeps the working directory unless it is a login shell, and --keep-cwd is
|
|
274
|
+
# how pkexec does the same. pkexec is silent when the dialog is dismissed
|
|
275
|
+
# (status 126), so say what the status means.
|
|
276
|
+
pkexec --keep-cwd --user "$user" sh -c "$cmd"
|
|
277
|
+
status=$?
|
|
278
|
+
if [ $status -eq 126 ]; then
|
|
279
|
+
printf 'pi-polkit: %s\\n' 'the polkit authentication dialog was dismissed' >&2
|
|
280
|
+
fi
|
|
281
|
+
exit $status
|
|
282
|
+
`
|
|
283
|
+
|
|
177
284
|
/** sudoedit edits through sudo's own temporary-file flow; polkit has none. */
|
|
178
285
|
const SUDOEDIT_SHIM = `#!/bin/sh
|
|
179
286
|
printf 'pi-polkit: sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.\\n' >&2
|
|
@@ -190,6 +297,7 @@ exit ${SHIM_USAGE_EXIT}
|
|
|
190
297
|
const POLKIT_SHIM_SCRIPTS: Record<string, string> = {
|
|
191
298
|
sudo: SUDO_SHIM,
|
|
192
299
|
doas: DOAS_SHIM,
|
|
300
|
+
su: SU_SHIM,
|
|
193
301
|
sudoedit: SUDOEDIT_SHIM,
|
|
194
302
|
visudo: VISUDO_SHIM,
|
|
195
303
|
}
|
|
@@ -198,8 +306,8 @@ const POLKIT_SHIM_SCRIPTS: Record<string, string> = {
|
|
|
198
306
|
let shimDirectory: string | undefined
|
|
199
307
|
|
|
200
308
|
/**
|
|
201
|
-
* Returns the shim directory, writing the
|
|
202
|
-
*
|
|
309
|
+
* Returns the shim directory, writing the stand-in scripts on first use.
|
|
310
|
+
* Creation is lazy so factory-only invocations (runs that never start a
|
|
203
311
|
* session) leave nothing behind.
|
|
204
312
|
*/
|
|
205
313
|
export function ensurePolkitShims(): string {
|
|
@@ -219,7 +327,7 @@ export function removePolkitShims(): void {
|
|
|
219
327
|
|
|
220
328
|
/**
|
|
221
329
|
* Returns the environment with the shim directory first on PATH, so the shell
|
|
222
|
-
* resolves sudo and
|
|
330
|
+
* resolves sudo, doas and su to the shims instead of the system binaries. The
|
|
223
331
|
* prepend is idempotent: an environment that already carries the prefix is
|
|
224
332
|
* returned unchanged.
|
|
225
333
|
*/
|