pi-polkit 0.2.0 → 0.2.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 +33 -0
- package/README.md +73 -22
- package/package.json +3 -2
- package/polkit-redirect.ts +4 -4
- package/polkit-shims.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ 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.2.1] - 2026-11-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `test/polkit-shims.test.ts`: behaviour tests for the generated shims. They
|
|
13
|
+
stub `pkexec`, `pkcheck` and `pkaction` on `PATH`, so they need neither polkit,
|
|
14
|
+
a desktop agent nor root; `npm test` runs them with Node's test runner.
|
|
15
|
+
- README: account-lockout section. Where the `polkit-1` PAM service includes
|
|
16
|
+
`pam_faillock`, a wrong or empty password submitted in the dialog adds a
|
|
17
|
+
record shared with terminal sudo and login; dismissing the dialog adds none,
|
|
18
|
+
an agent may allow several attempts in one dialog (each retry starts a new PAM
|
|
19
|
+
helper), and a locked account rejects even the correct password for
|
|
20
|
+
`unlock_time`. Documents how to inspect and reset the tally, and why retrying
|
|
21
|
+
does not help.
|
|
22
|
+
- README limitation: `-n`, `-v` and `-l` check
|
|
23
|
+
`org.freedesktop.policykit.exec` itself, while pkexec may select another
|
|
24
|
+
action installed with `org.freedesktop.policykit.exec.path`/`argv1`
|
|
25
|
+
annotations.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- The bash tool's prompt guideline now asks the model to consult the user before
|
|
30
|
+
retrying a dismissed or failed dialog, because failed or empty password
|
|
31
|
+
submissions count toward a system-wide `pam_faillock` lockout while a dismissal
|
|
32
|
+
does not.
|
|
33
|
+
- README: `sudoers` and the `sudo` PAM stack are not consulted; polkit policy and
|
|
34
|
+
the `polkit-1` PAM service decide, so sudo-only modules such as a fingerprint
|
|
35
|
+
reader or a second factor do not run in Pi.
|
|
36
|
+
- README now describes agent caching and retry behaviour generically instead of
|
|
37
|
+
attributing it to one desktop agent.
|
|
38
|
+
- `sudo -v` reports an interactive failure as "polkit did not authorize" instead
|
|
39
|
+
of "polkit denies", since the check can fail after the password is rejected.
|
|
40
|
+
|
|
8
41
|
## [0.2.0] - 2026-11-05
|
|
9
42
|
|
|
10
43
|
### Added
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# pi-polkit
|
|
2
2
|
|
|
3
3
|
Redirects `sudo`, `doas` and `su -c` to `pkexec` inside Pi, so authorisation
|
|
4
|
-
goes through the desktop's polkit agent
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
goes through the desktop's polkit agent instead of a terminal the shell tools do
|
|
5
|
+
not have. Applies to the `bash` tool and to `!` user commands; `sudo` in other
|
|
6
|
+
terminals is unchanged.
|
|
7
7
|
|
|
8
8
|
## Scope
|
|
9
9
|
|
|
@@ -47,7 +47,7 @@ pi -e ./pi-polkit
|
|
|
47
47
|
| `sudo -u bob …` | as above, as `bob` |
|
|
48
48
|
| `cat x \| sudo tee y` | dialog for `tee` |
|
|
49
49
|
| `sudo -n …` | runs only if polkit would authorise without asking; otherwise exit 1 with `sudo: a password is required` |
|
|
50
|
-
| `sudo -v` |
|
|
50
|
+
| `sudo -v` | authenticates through polkit; creates no timestamp |
|
|
51
51
|
| `sudo -l` | prints the polkit policy and this session's temporary authorizations |
|
|
52
52
|
| `sudo -k`, `-K` | revokes polkit's temporary authorizations |
|
|
53
53
|
| `sudo -H`, `-N`, `-P` | accepted: pkexec already does what they ask |
|
|
@@ -93,20 +93,66 @@ pkexec stripped.
|
|
|
93
93
|
one; other actions may not, according to policy. The extension takes no part in
|
|
94
94
|
that decision.
|
|
95
95
|
|
|
96
|
+
`sudoers` is not consulted: polkit policy decides who may run what, so per-user
|
|
97
|
+
and per-command rules, `NOPASSWD`, `secure_path` and `env_keep` do not apply.
|
|
98
|
+
Authentication runs through the `polkit-1` PAM service, not `/etc/pam.d/sudo`,
|
|
99
|
+
so a module configured only for sudo — a fingerprint reader, a second factor —
|
|
100
|
+
is not used here; the service's own stack (normally `system-auth`) decides how
|
|
101
|
+
the password is verified.
|
|
102
|
+
|
|
96
103
|
`org.freedesktop.policykit.exec` asks for administrator authentication on every
|
|
97
|
-
call, but
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
agent
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`pkcheck
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
call, but an agent may keep the password it just received and answer a later
|
|
105
|
+
challenge itself. In that case a second command can run without a dialog. That
|
|
106
|
+
cache belongs to the agent: a different agent, a restarted agent or a change in
|
|
107
|
+
the agent's state can mean the next command prompts again, so do not rely on it.
|
|
108
|
+
|
|
109
|
+
With the default policy, polkit records no temporary authorization for this
|
|
110
|
+
action: after a successful `pkexec`, `pkcheck --list-temp` stays empty and a
|
|
111
|
+
non-interactive `pkcheck` still reports that authentication is required. Whether
|
|
112
|
+
a later command prompts is therefore the agent's decision, and no user-space
|
|
113
|
+
query can predict it. That is why `sudo -n` reports what the policy says rather
|
|
114
|
+
than guessing whether a dialog would appear, and why `sudo -k` can only clear
|
|
115
|
+
polkit's own record.
|
|
116
|
+
|
|
117
|
+
The extension keeps no authorization state of its own: `sudo -v` verifies once
|
|
118
|
+
and writes nothing, `sudo -n` assumes nothing, and `sudo -k` only clears polkit's
|
|
119
|
+
record. Authorising one command therefore leaves the next one to be decided
|
|
120
|
+
again, which is the point — the person who allowed an upgrade can still refuse
|
|
121
|
+
the deletion that follows. What widens that window is the agent's password
|
|
122
|
+
cache, not this extension, and nothing here can revoke it.
|
|
123
|
+
|
|
124
|
+
## Account lockout
|
|
125
|
+
|
|
126
|
+
When the `polkit-1` PAM service includes `pam_faillock` (Arch's default stack
|
|
127
|
+
does), the dialog shares one failure tally per user with terminal sudo, `su` and
|
|
128
|
+
the login screen:
|
|
129
|
+
|
|
130
|
+
- A wrong or empty password submitted in the dialog adds a record. Dismissing
|
|
131
|
+
the dialog, closing it or pressing Esc cancels the agent's helper before PAM
|
|
132
|
+
returns, and adds none. (An agent process that is killed instead of
|
|
133
|
+
cancelling leaves the helper at EOF, and that attempt can count.)
|
|
134
|
+
- An agent may allow several attempts in one dialog, and each retry starts a
|
|
135
|
+
new PAM helper; a dialog can therefore add several records and use up the
|
|
136
|
+
default `deny=3`.
|
|
137
|
+
- Once a user has `deny` failures within `fail_interval` (900 s by default),
|
|
138
|
+
`pam_faillock` refuses that user in every service using the stack for
|
|
139
|
+
`unlock_time` (600 s by default, measured from the newest record). A locked
|
|
140
|
+
account rejects even the correct password, because `preauth` fails and
|
|
141
|
+
`authsucc` cannot reset the tally; further attempts neither extend nor shorten
|
|
142
|
+
the lock. A successful password authentication outside a lock resets the
|
|
143
|
+
tally.
|
|
144
|
+
- `sudo -k` clears polkit's authorizations, not the tally. The locked user's own
|
|
145
|
+
authentication cannot succeed, so Pi cannot elevate with that user's
|
|
146
|
+
credentials to reset it: wait for the lock to expire, use a root console or
|
|
147
|
+
another administrator, or reboot (`/run` is cleared). Where the tally file is
|
|
148
|
+
readable by the user, `faillock --user "$USER"` shows the records and
|
|
149
|
+
`faillock --user "$USER" --reset` clears them without root; otherwise both
|
|
150
|
+
need root. Check readability with `ls -l /run/faillock/$USER`.
|
|
151
|
+
|
|
152
|
+
Retrying a failed dialog in a loop only gives the user more chances to submit a
|
|
153
|
+
wrong or empty password, and the lock applies to the whole account. The prompt
|
|
154
|
+
guideline therefore tells the model to ask before retrying, and `sudo -n` remains
|
|
155
|
+
the probe that never invokes PAM.
|
|
110
156
|
|
|
111
157
|
## Limitations
|
|
112
158
|
|
|
@@ -116,6 +162,10 @@ dialog would appear, and why `sudo -k` can only clear polkit's own record.
|
|
|
116
162
|
- `su -c … USER` authenticates an administrator (polkit), not `USER`, so an
|
|
117
163
|
account that real `su` would allow can be refused here.
|
|
118
164
|
- `su -c` runs the command with `sh -c`, not with the target user's login shell.
|
|
165
|
+
- `-n`, `-v` and `-l` check `org.freedesktop.policykit.exec` itself. pkexec may
|
|
166
|
+
select a different action installed with an
|
|
167
|
+
`org.freedesktop.policykit.exec.path` (and `argv1`) annotation, so that
|
|
168
|
+
action's rules are not reflected by these checks.
|
|
119
169
|
- `sudo -E` re-exports the environment through `pkexec`'s argument list, so the
|
|
120
170
|
values are visible in `ps` while the command starts. Exported shell functions
|
|
121
171
|
are skipped, and `PATH` loses the shim directories.
|
|
@@ -135,18 +185,19 @@ without one, `pkexec` fails instead of prompting.
|
|
|
135
185
|
|
|
136
186
|
| Status | Meaning |
|
|
137
187
|
|--------|--------------------------------------------------------|
|
|
138
|
-
| 1 | `-n` or `-v` could not be satisfied: polkit requires authentication, or
|
|
188
|
+
| 1 | `-n` or `-v` could not be satisfied: polkit requires authentication, denies it, or authentication failed |
|
|
139
189
|
| 126 | the polkit dialog was dismissed |
|
|
140
|
-
| 127 | not authorized, or the program could not run
|
|
190
|
+
| 127 | not authorized (including after failed authentication), or the program could not run |
|
|
141
191
|
| 64 | the extension refused the invocation |
|
|
142
192
|
|
|
143
193
|
Messages are in English.
|
|
144
194
|
|
|
145
195
|
## Development
|
|
146
196
|
|
|
147
|
-
`npm install
|
|
148
|
-
`@earendil-works/pi-coding-agent` 1.4.0, the API Pi serves to Node
|
|
149
|
-
|
|
197
|
+
`npm install`, then `npm run check` type-checks against the pinned
|
|
198
|
+
`@earendil-works/pi-coding-agent` 1.4.0, the API Pi serves to Node extensions,
|
|
199
|
+
and `npm test` runs the shim behaviour tests. The tests stub `pkexec`, `pkcheck`
|
|
200
|
+
and `pkaction` on `PATH`, so they need neither polkit, a desktop agent nor root.
|
|
150
201
|
|
|
151
202
|
## License
|
|
152
203
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-polkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
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": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
17
|
-
"
|
|
17
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
18
|
+
"prepublishOnly": "npm run check && npm test"
|
|
18
19
|
},
|
|
19
20
|
"peerDependencies": {
|
|
20
21
|
"@earendil-works/pi-coding-agent": "*"
|
package/polkit-redirect.ts
CHANGED
|
@@ -4,9 +4,9 @@ import { polkitShimsPathExportLine, removePolkitShims, withPolkitShimsOnPath } f
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Redirects sudo, doas and su -c to pkexec inside Pi, so authorisation goes
|
|
7
|
-
* through the desktop's polkit agent
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* through the desktop's polkit agent instead of a terminal the shell tools do
|
|
8
|
+
* not have. Applies to the `bash` tool and to `!` user commands; `sudo` in other
|
|
9
|
+
* terminals is unchanged.
|
|
10
10
|
*
|
|
11
11
|
* The redirect is a PATH prepend, not a command rewrite: the shims in
|
|
12
12
|
* polkit-shims.ts stand in for the sudo/doas/su binaries, and the shell's own
|
|
@@ -25,7 +25,7 @@ export default function polkitRedirect(pi: ExtensionAPI): void {
|
|
|
25
25
|
// The model plans privilege with what it knows about sudo. Say once, in the
|
|
26
26
|
// system prompt, how this shell differs, so it does not learn it by failing.
|
|
27
27
|
bash.promptGuidelines?.push(
|
|
28
|
-
"`sudo`, `doas` and `su -c` are routed to pkexec, so a desktop polkit dialog authorises them instead of a terminal password prompt. `sudo -n` runs the command only if polkit would authorise it without asking, `sudo -v` performs
|
|
28
|
+
"`sudo`, `doas` and `su -c` are routed to pkexec, so a desktop polkit dialog authorises them instead of a terminal password prompt. `sudo -n` runs the command only if polkit would authorise it without asking, `sudo -v` performs a real authentication, and `sudo -l` prints the polkit policy that decides. Ask the user before retrying a dismissed or failed dialog instead of looping: on systems with pam_faillock, failed or empty password submissions count toward a system-wide lockout shared with login, while dismissing the dialog does not.",
|
|
29
29
|
)
|
|
30
30
|
pi.registerTool(bash)
|
|
31
31
|
|
package/polkit-shims.ts
CHANGED
|
@@ -309,7 +309,7 @@ if [ "$mode" = validate ]; then
|
|
|
309
309
|
exit 0
|
|
310
310
|
;;
|
|
311
311
|
1)
|
|
312
|
-
printf 'pi-polkit: sudo: polkit
|
|
312
|
+
printf 'pi-polkit: sudo: polkit did not authorize %s (the policy denies it, or authentication failed)\\n' "$ACTION" >&2
|
|
313
313
|
exit 1
|
|
314
314
|
;;
|
|
315
315
|
2)
|